summaryrefslogtreecommitdiff
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/Android.bp53
-rw-r--r--services/accessibility/accessibility.aconfig46
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java315
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java93
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java301
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java90
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java93
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java111
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java74
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyManager.java78
-rw-r--r--services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java93
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java131
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java45
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java89
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java11
-rw-r--r--services/appfunctions/Android.bp11
-rw-r--r--services/appfunctions/TEST_MAPPING5
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java188
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java25
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java495
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java74
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java12
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java38
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java27
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java29
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java14
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java59
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java62
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java36
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java40
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java36
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java54
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java93
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java7
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java13
-rw-r--r--services/appfunctions/lint-baseline.xml26
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java903
-rw-r--r--services/art-profile2
-rw-r--r--services/art-wear-profile4
-rw-r--r--services/autofill/bugfixes.aconfig37
-rw-r--r--services/autofill/features.aconfig38
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java11
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java26
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java82
-rw-r--r--services/autofill/java/com/android/server/autofill/Helper.java60
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java9
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java3339
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java5
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java5
-rw-r--r--services/backup/flags.aconfig25
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java417
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java6
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java6
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java242
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java5
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java3
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java48
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java5
-rw-r--r--services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java26
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java77
-rw-r--r--services/backup/java/com/android/server/backup/transport/BackupTransportClient.java22
-rw-r--r--services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java7
-rw-r--r--services/companion/java/com/android/server/companion/BackupRestoreProcessor.java7
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java240
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java4
-rw-r--r--services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java225
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java96
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java20
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java16
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java13
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java27
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/SecureChannel.java12
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java40
-rw-r--r--services/companion/java/com/android/server/companion/transport/Transport.java32
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/CameraAccessController.java13
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java21
-rw-r--r--services/companion/java/com/android/server/companion/virtual/SensorController.java9
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java384
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java119
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java22
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java17
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java14
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java24
-rw-r--r--services/core/Android.bp23
-rw-r--r--services/core/java/android/os/BatteryStatsInternal.java13
-rw-r--r--services/core/java/com/android/server/BatteryService.java454
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java188
-rw-r--r--services/core/java/com/android/server/BootReceiver.java14
-rw-r--r--services/core/java/com/android/server/DockObserver.java18
-rw-r--r--services/core/java/com/android/server/EventLogTags.logtags4
-rw-r--r--services/core/java/com/android/server/ExplicitHealthCheckController.java466
-rw-r--r--services/core/java/com/android/server/FgThread.java79
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java2083
-rw-r--r--services/core/java/com/android/server/RescueParty.java1090
-rw-r--r--services/core/java/com/android/server/ServiceThread.java52
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java15
-rw-r--r--services/core/java/com/android/server/SystemConfig.java14
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java500
-rw-r--r--services/core/java/com/android/server/TradeInModeService.java344
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java65
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java74
-rw-r--r--services/core/java/com/android/server/adaptiveauth/OWNERS1
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java182
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java41
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java587
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java57
-rw-r--r--services/core/java/com/android/server/am/AnrHelper.java12
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java4
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java2
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java93
-rw-r--r--services/core/java/com/android/server/am/BackupRecord.java5
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java214
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java56
-rw-r--r--services/core/java/com/android/server/am/BroadcastFilter.java54
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java155
-rw-r--r--services/core/java/com/android/server/am/BroadcastSkipPolicy.java100
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java2
-rw-r--r--services/core/java/com/android/server/am/ContentProviderConnection.java18
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java30
-rw-r--r--services/core/java/com/android/server/am/FgsTempAllowList.java17
-rw-r--r--services/core/java/com/android/server/am/OWNERS4
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java530
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java33
-rw-r--r--services/core/java/com/android/server/am/PendingIntentController.java3
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java18
-rw-r--r--services/core/java/com/android/server/am/PhantomProcessList.java43
-rw-r--r--services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java8
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java4
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java116
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java13
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java12
-rw-r--r--services/core/java/com/android/server/am/ProcessStateController.java661
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java12
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java2
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java56
-rw-r--r--services/core/java/com/android/server/am/UserController.java320
-rw-r--r--services/core/java/com/android/server/am/broadcasts_flags.aconfig18
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig111
-rw-r--r--services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java27
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java319
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java20
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java248
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioServerPermissionProvider.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java715
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java28
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java9
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java172
-rw-r--r--services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java182
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java29
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java90
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java27
-rw-r--r--services/core/java/com/android/server/backup/SystemBackupAgent.java1
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java14
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java72
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java36
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java10
-rw-r--r--services/core/java/com/android/server/biometrics/biometrics.aconfig7
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java25
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricLogger.java53
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/EnrollClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java47
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/RemovalClient.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java4
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java98
-rw-r--r--services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java24
-rw-r--r--services/core/java/com/android/server/compat/platform_compat_flags.aconfig10
-rw-r--r--services/core/java/com/android/server/content/SyncLogger.java4
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java3
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java55
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java85
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java240
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java11
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java3
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java4
-rw-r--r--services/core/java/com/android/server/display/BrightnessTracker.java73
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java118
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java45
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayGroup.java10
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java779
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java185
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java180
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java48
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayStatsService.java22
-rw-r--r--services/core/java/com/android/server/display/HighBrightnessModeController.java18
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java109
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java7
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java124
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java52
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java160
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessEvent.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessReason.java7
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java44
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java24
-rw-r--r--services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java15
-rw-r--r--services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java15
-rw-r--r--services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java25
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java78
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java162
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java (renamed from services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java)184
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java2
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java46
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java2
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java3
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java58
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java56
-rw-r--r--services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java8
-rw-r--r--services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java4
-rw-r--r--services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java78
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java152
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig132
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java123
-rw-r--r--services/core/java/com/android/server/display/mode/SyntheticModeManager.java9
-rw-r--r--services/core/java/com/android/server/display/mode/SystemRequestObserver.java16
-rw-r--r--services/core/java/com/android/server/display/mode/Vote.java2
-rw-r--r--services/core/java/com/android/server/display/plugin/Plugin.java44
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginEventStorage.java134
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginManager.java141
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginStorage.java144
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginType.java48
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginsProvider.java36
-rw-r--r--services/core/java/com/android/server/display/state/DisplayStateController.java2
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java95
-rw-r--r--services/core/java/com/android/server/flags/pinner.aconfig13
-rw-r--r--services/core/java/com/android/server/flags/services.aconfig11
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java27
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java55
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java10
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java74
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java13
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java138
-rw-r--r--services/core/java/com/android/server/hdmi/PowerManagerWrapper.java8
-rw-r--r--services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java23
-rw-r--r--services/core/java/com/android/server/incident/PendingReports.java28
-rw-r--r--services/core/java/com/android/server/incident/TEST_MAPPING10
-rw-r--r--services/core/java/com/android/server/input/AppLaunchShortcutManager.java339
-rw-r--r--services/core/java/com/android/server/input/InputDataStore.java344
-rw-r--r--services/core/java/com/android/server/input/InputFeatureFlagProvider.java101
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java470
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java54
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java307
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java32
-rw-r--r--services/core/java/com/android/server/input/InputShellCommand.java24
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java849
-rw-r--r--services/core/java/com/android/server/input/KeyboardBacklightController.java91
-rw-r--r--services/core/java/com/android/server/input/KeyboardGlyphManager.java14
-rw-r--r--services/core/java/com/android/server/input/KeyboardLedController.java62
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java49
-rw-r--r--services/core/java/com/android/server/input/PointerIconCache.java37
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadDebugView.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java26
-rw-r--r--services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java43
-rw-r--r--services/core/java/com/android/server/inputmethod/HandwritingModeController.java10
-rw-r--r--services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeBindingState.java9
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodBindingController.java22
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java1
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java331
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java443
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java33
-rw-r--r--services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java9
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java1
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java2
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerShellCommand.java57
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java37
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java21
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java9
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java248
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java302
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java4
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java122
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java164
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java50
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java586
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java475
-rw-r--r--services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java342
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java221
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudger.java63
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudgerCache.java200
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java14
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java120
-rw-r--r--services/core/java/com/android/server/media/AudioManagerRouteController.java30
-rw-r--r--services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2Provider.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java21
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java17
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java293
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java12
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java8
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java85
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java13
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java124
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java11
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java41
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java183
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionStopController.java239
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java64
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java534
-rw-r--r--services/core/java/com/android/server/media/quality/OWNERS2
-rw-r--r--services/core/java/com/android/server/notification/CalendarTracker.java6
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java11
-rw-r--r--services/core/java/com/android/server/notification/CountdownConditionProvider.java6
-rw-r--r--services/core/java/com/android/server/notification/CustomManualConditionProvider.java6
-rw-r--r--services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java99
-rw-r--r--services/core/java/com/android/server/notification/EventConditionProvider.java81
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java616
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java59
-rw-r--r--services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java28
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java117
-rw-r--r--services/core/java/com/android/server/notification/NotificationBackupHelper.java89
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java1092
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java58
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationShellCmd.java8
-rw-r--r--services/core/java/com/android/server/notification/NotificationSignalExtractor.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationUsageStats.java1
-rw-r--r--services/core/java/com/android/server/notification/PermissionHelper.java21
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java263
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java2
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java4
-rw-r--r--services/core/java/com/android/server/notification/RateEstimator.java61
-rw-r--r--services/core/java/com/android/server/notification/ScheduleConditionProvider.java53
-rw-r--r--services/core/java/com/android/server/notification/SystemConditionProviderService.java8
-rw-r--r--services/core/java/com/android/server/notification/TimeToLiveHelper.java6
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java16
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java8
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java555
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig38
-rw-r--r--services/core/java/com/android/server/om/OverlayActorEnforcer.java9
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java407
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java101
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OWNERS1
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java1106
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java144
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java66
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java76
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java97
-rw-r--r--services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java119
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSource.java (renamed from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java)15
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSourceStatic.java37
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSourceStream.java43
-rw-r--r--services/core/java/com/android/server/pinner/PinnedFile.java61
-rw-r--r--services/core/java/com/android/server/pinner/PinnerService.java (renamed from services/core/java/com/android/server/PinnerService.java)589
-rw-r--r--services/core/java/com/android/server/pinner/PinnerUtils.java75
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java12
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java32
-rw-r--r--services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java152
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java290
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java41
-rw-r--r--services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java4
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java15
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java21
-rw-r--r--services/core/java/com/android/server/pm/DistractingPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/InstallDependencyHelper.java478
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java387
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java29
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java112
-rw-r--r--services/core/java/com/android/server/pm/PackageAbiHelper.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageAbiHelperImpl.java19
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java50
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java110
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java129
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java11
-rw-r--r--services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java107
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java91
-rw-r--r--services/core/java/com/android/server/pm/RemovePackageHelper.java10
-rw-r--r--services/core/java/com/android/server/pm/SaferIntentUtils.java117
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java41
-rw-r--r--services/core/java/com/android/server/pm/Settings.java15
-rw-r--r--services/core/java/com/android/server/pm/SharedLibrariesImpl.java81
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java8
-rw-r--r--services/core/java/com/android/server/pm/StorageEventHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java16
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING48
-rw-r--r--services/core/java/com/android/server/pm/UserDataPreparer.java29
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java294
-rw-r--r--services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java40
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageCacher.java31
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java32
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java22
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java16
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java10
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java4
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java5
-rw-r--r--services/core/java/com/android/server/policy/KeyCombinationManager.java30
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java126
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java559
-rw-r--r--services/core/java/com/android/server/policy/TalkbackShortcutController.java13
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java11
-rw-r--r--services/core/java/com/android/server/policy/WindowWakeUpPolicy.java37
-rw-r--r--services/core/java/com/android/server/power/FrameworkStatsLogger.java62
-rw-r--r--services/core/java/com/android/server/power/Notifier.java146
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java142
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java151
-rw-r--r--services/core/java/com/android/server/power/PowerManagerShellCommand.java22
-rw-r--r--services/core/java/com/android/server/power/ThermalManagerService.java515
-rw-r--r--services/core/java/com/android/server/power/WakefulnessSessionObserver.java103
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java5
-rw-r--r--services/core/java/com/android/server/power/feature/PowerManagerFlags.java48
-rw-r--r--services/core/java/com/android/server/power/feature/power_flags.aconfig32
-rw-r--r--services/core/java/com/android/server/power/hint/Android.bp9
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java711
-rw-r--r--services/core/java/com/android/server/power/hint/adpf_flags.aconfig15
-rw-r--r--services/core/java/com/android/server/power/hint/flags.aconfig14
-rw-r--r--services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java72
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryChargeCalculator.java8
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java187
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java231
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java416
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java11
-rw-r--r--services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java2
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java3
-rw-r--r--services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java3
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsSpan.java50
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsStore.java46
-rw-r--r--services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java7
-rw-r--r--services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java130
-rw-r--r--services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java339
-rw-r--r--services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java6
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig11
-rw-r--r--services/core/java/com/android/server/power/stats/format/BasePowerStatsLayout.java105
-rw-r--r--services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java2
-rw-r--r--services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java14
-rw-r--r--services/core/java/com/android/server/power/stats/format/WakelockPowerStatsLayout.java31
-rw-r--r--services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java7
-rw-r--r--services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java26
-rw-r--r--services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java12
-rw-r--r--services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java6
-rw-r--r--services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java173
-rw-r--r--services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java16
-rw-r--r--services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java6
-rw-r--r--services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java71
-rw-r--r--services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java6
-rw-r--r--services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java8
-rw-r--r--services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java39
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java9
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java22
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java128
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java8
-rw-r--r--services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java9
-rw-r--r--services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java5
-rw-r--r--services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java5
-rw-r--r--services/core/java/com/android/server/power/stats/processor/WakelockPowerStatsProcessor.java63
-rw-r--r--services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java6
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java4
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java12
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java819
-rw-r--r--services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java254
-rw-r--r--services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java3
-rw-r--r--services/core/java/com/android/server/security/AttestationVerificationManagerService.java18
-rw-r--r--services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java156
-rw-r--r--services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java80
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java341
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java82
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java35
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java27
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java125
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java122
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java81
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java (renamed from services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java)59
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/OWNERS3
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java120
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java55
-rw-r--r--services/core/java/com/android/server/security/forensic/OWNERS1
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java150
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/DataSource.java29
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java240
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java336
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java147
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/OWNERS1
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java97
-rw-r--r--services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java368
-rw-r--r--services/core/java/com/android/server/servicewatcher/OWNERS5
-rw-r--r--services/core/java/com/android/server/servicewatcher/ServiceWatcher.java255
-rw-r--r--services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java318
-rw-r--r--services/core/java/com/android/server/stats/Android.bp7
-rw-r--r--services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java36
-rw-r--r--services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java127
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java205
-rw-r--r--services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java124
-rw-r--r--services/core/java/com/android/server/stats/pull/psi/OWNERS9
-rw-r--r--services/core/java/com/android/server/stats/pull/psi/PsiData.java106
-rw-r--r--services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java161
-rw-r--r--services/core/java/com/android/server/stats/stats_flags.aconfig16
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java50
-rw-r--r--services/core/java/com/android/server/telecom/TelecomLoaderService.java6
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java19
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java3
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java210
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java8
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java2
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java69
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java2
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java12
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java3
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java8
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java226
-rw-r--r--services/core/java/com/android/server/uri/NeededUriGrants.java25
-rw-r--r--services/core/java/com/android/server/uri/UriPermission.java64
-rw-r--r--services/core/java/com/android/server/vibrator/AbstractVibratorStep.java8
-rw-r--r--services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java327
-rw-r--r--services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java8
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java162
-rw-r--r--services/core/java/com/android/server/vibrator/DeviceAdapter.java29
-rw-r--r--services/core/java/com/android/server/vibrator/ExternalVibrationSession.java109
-rw-r--r--services/core/java/com/android/server/vibrator/HalVibration.java126
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java13
-rw-r--r--services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java116
-rw-r--r--services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java59
-rw-r--r--services/core/java/com/android/server/vibrator/PwleSegmentsValidator.java71
-rw-r--r--services/core/java/com/android/server/vibrator/SingleVibrationSession.java176
-rw-r--r--services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java108
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java593
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java77
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSegmentsValidator.java35
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSession.java95
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java12
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStats.java66
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java66
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationThread.java68
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorController.java146
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java98
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java1376
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java19
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperData.java44
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java113
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java345
-rw-r--r--services/core/java/com/android/server/wearable/RemoteWearableSensingService.java88
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java297
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java179
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java49
-rw-r--r--services/core/java/com/android/server/webkit/SystemInterface.java7
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateService.java58
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java768
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java42
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java52
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java103
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java689
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java329
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecordInputSink.java34
-rw-r--r--services/core/java/com/android/server/wm/ActivityRefresher.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java69
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java276
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java189
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java77
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java77
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java30
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraOverrides.java26
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraPolicy.java131
-rw-r--r--services/core/java/com/android/server/wm/AppCompatConfiguration.java60
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java39
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java241
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java79
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java107
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java11
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java51
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java83
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java11
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java6
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java4
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java76
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java4
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java2
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java147
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java322
-rw-r--r--services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java9
-rw-r--r--services/core/java/com/android/server/wm/BlackFrame.java2
-rw-r--r--services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java202
-rw-r--r--services/core/java/com/android/server/wm/CameraStateMonitor.java49
-rw-r--r--services/core/java/com/android/server/wm/ClientLifecycleManager.java42
-rw-r--r--services/core/java/com/android/server/wm/CompatModePackages.java2
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java22
-rw-r--r--services/core/java/com/android/server/wm/ContentRecordingController.java2
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java6
-rw-r--r--services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java21
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java15
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java2
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java33
-rw-r--r--services/core/java/com/android/server/wm/DimmerAnimationHelper.java66
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java39
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java198
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java105
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java75
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCoordinator.java22
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java34
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationReversionController.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java42
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java2
-rw-r--r--services/core/java/com/android/server/wm/DragState.java6
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java38
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags13
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java136
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java63
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java5
-rw-r--r--services/core/java/com/android/server/wm/InsetsControlTarget.java5
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java22
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java63
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java24
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java42
-rw-r--r--services/core/java/com/android/server/wm/LaunchParamsPersister.java263
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java97
-rw-r--r--services/core/java/com/android/server/wm/LockTaskController.java2
-rw-r--r--services/core/java/com/android/server/wm/MirrorActiveUids.java47
-rw-r--r--services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java2
-rw-r--r--services/core/java/com/android/server/wm/PageSizeMismatchDialog.java72
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java68
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java6
-rw-r--r--services/core/java/com/android/server/wm/RefreshRatePolicy.java46
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java2
-rw-r--r--services/core/java/com/android/server/wm/RemoteDisplayChangeController.java2
-rw-r--r--services/core/java/com/android/server/wm/ResetTargetTaskHelper.java4
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java119
-rw-r--r--services/core/java/com/android/server/wm/SafeActivityOptions.java45
-rw-r--r--services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java2
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java39
-rw-r--r--services/core/java/com/android/server/wm/SeamlessRotator.java2
-rw-r--r--services/core/java/com/android/server/wm/Session.java19
-rw-r--r--services/core/java/com/android/server/wm/SnapshotController.java5
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java61
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java64
-rw-r--r--services/core/java/com/android/server/wm/SurfaceFreezer.java2
-rw-r--r--services/core/java/com/android/server/wm/Task.java229
-rw-r--r--services/core/java/com/android/server/wm/TaskChangeNotificationController.java18
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java23
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java48
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java10
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java30
-rw-r--r--services/core/java/com/android/server/wm/Transition.java378
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java106
-rw-r--r--services/core/java/com/android/server/wm/TransparentPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/TrustedPresentationListenerController.java42
-rw-r--r--services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java2
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java2
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java128
-rw-r--r--services/core/java/com/android/server/wm/WindowContainerThumbnail.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContextListenerController.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerConstants.java74
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java277
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java106
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java115
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java134
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java19
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java64
-rw-r--r--services/core/java/com/android/server/wm/WindowTracing.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowTracingPerfetto.java12
-rw-r--r--services/core/java/com/android/server/wm/utils/RegionUtils.java16
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp229
-rw-r--r--services/core/jni/com_android_server_utils_AnrTimer.cpp3
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorController.cpp108
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp131
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/core/lint-baseline.xml673
-rw-r--r--services/core/services-jarjar-rules.txt2
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd11
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt9
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java21
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java40
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2724
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java17
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java39
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java38
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java19
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java74
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java174
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java18
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java41
-rw-r--r--services/java/com/android/server/SystemServer.java166
-rw-r--r--services/java/com/android/server/flags.aconfig16
-rw-r--r--services/manifest_services.xml5
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java6
-rw-r--r--services/people/java/com/android/server/people/data/CallLogQueryHelper.java2
-rw-r--r--services/people/java/com/android/server/people/data/ContactsQueryHelper.java8
-rw-r--r--services/people/java/com/android/server/people/data/MmsQueryHelper.java4
-rw-r--r--services/people/java/com/android/server/people/data/SmsQueryHelper.java2
-rw-r--r--services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java3
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt1
-rw-r--r--services/proguard.flags5
-rw-r--r--services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java10
-rw-r--r--services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java46
-rw-r--r--services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java36
-rw-r--r--services/supervision/Android.bp3
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java206
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java32
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionUserData.java45
-rw-r--r--services/supervision/lint-baseline.xml48
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/Android.bp44
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml27
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING7
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java24
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java23
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java40
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java33
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java23
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java144
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java29
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java12
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java4
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java21
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMenuControllerTest.java234
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java14
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt5
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java7
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java7
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java238
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt8
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java69
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java4
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt52
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt3
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt1
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt1
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt1
-rw-r--r--services/tests/appfunctions/Android.bp3
-rw-r--r--services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt35
-rw-r--r--services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt78
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt89
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt5
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java85
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java100
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java188
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java584
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java302
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt108
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java59
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java151
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java48
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java25
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java318
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java36
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java101
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java210
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java239
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java243
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java24
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java24
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java105
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java58
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt13
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java129
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java103
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt29
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt24
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt97
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt100
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java61
-rw-r--r--services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java54
-rw-r--r--services/tests/mockingservicestests/Android.bp36
-rw-r--r--services/tests/mockingservicestests/AndroidTest.xml6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java391
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java66
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java439
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java86
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java246
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java94
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java24
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java221
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java390
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java175
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java441
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java1035
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java287
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java475
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java91
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java113
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java27
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java102
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java126
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java270
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java68
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java1881
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java91
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java37
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java341
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java138
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java70
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java132
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt16
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java204
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java52
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java89
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java104
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java108
-rw-r--r--services/tests/ondeviceintelligencetests/Android.bp8
-rw-r--r--services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java26
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java540
-rw-r--r--services/tests/powerservicetests/Android.bp1
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java3
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java517
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java209
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java296
-rw-r--r--services/tests/powerstatstests/res/xml/irq_device_map_3.xml3
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java11
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java4
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java38
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java41
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java4
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java14
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java136
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java290
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java34
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java170
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java40
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java21
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java14
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java50
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java28
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java36
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java24
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java135
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java401
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java20
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java259
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java11
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java54
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java6
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java171
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java87
-rw-r--r--services/tests/security/forensic/OWNERS3
-rw-r--r--services/tests/security/intrusiondetection/Android.bp49
-rw-r--r--services/tests/security/intrusiondetection/AndroidManifest.xml35
-rw-r--r--services/tests/security/intrusiondetection/AndroidTest.xml37
-rw-r--r--services/tests/security/intrusiondetection/OWNERS3
-rw-r--r--services/tests/security/intrusiondetection/TEST_MAPPING7
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java673
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp42
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml26
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java58
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java40
-rw-r--r--services/tests/servicestests/Android.bp19
-rw-r--r--services/tests/servicestests/AndroidManifest.xml2
-rw-r--r--services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/PinnerServiceTest.java138
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java432
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java479
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java1444
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt32
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java201
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java133
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java150
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java104
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java120
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java56
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java302
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java84
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java63
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java860
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java89
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java359
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java207
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java196
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java468
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java281
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java73
-rw-r--r--services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java319
-rw-r--r--services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java119
-rw-r--r--services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java325
-rw-r--r--services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java (renamed from services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java)37
-rw-r--r--services/tests/servicestests/src/com/android/server/security/authenticationpolicy/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt189
-rw-r--r--services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java305
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt267
-rw-r--r--services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java364
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java317
-rw-r--r--services/tests/timetests/Android.bp1
-rw-r--r--services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java56
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java102
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java1111
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java31
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java55
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java371
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java283
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java259
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java1275
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java3
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java579
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java157
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java18
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java33
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java16
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java159
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java491
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java46
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java1931
-rw-r--r--services/tests/vibrator/AndroidManifest.xml3
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java260
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java15
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java195
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java13
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java15
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java13
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java13
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java765
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java64
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java1079
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java69
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java21
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java1
-rw-r--r--services/tests/wmtests/Android.bp3
-rw-r--r--services/tests/wmtests/AndroidManifest.xml4
-rw-r--r--services/tests/wmtests/res/xml/bookmarks.xml47
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java521
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java86
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java64
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java95
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java84
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java107
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java86
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java254
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java175
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java199
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java86
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java336
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java58
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DimmerTests.java120
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java59
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java119
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java42
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java57
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java91
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java178
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java78
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java98
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java67
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java556
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java69
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java200
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java232
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java75
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java112
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java49
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java30
-rw-r--r--services/usb/java/com/UsbDataSignalDisableRequesters.java36
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java2
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java90
-rw-r--r--services/usb/java/com/android/server/usb/UsbManagerInternal.java50
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java100
-rw-r--r--services/usb/java/com/android/server/usb/flags/usb_flags.aconfig7
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java10
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java9
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java26
1120 files changed, 82173 insertions, 33755 deletions
diff --git a/services/Android.bp b/services/Android.bp
index 9b7e7e959e5c..473911f08cf7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -188,6 +188,57 @@ art_profile_java_defaults {
},
}
+// Conditionally add crashrecovery stubs library
+soong_config_module_type {
+ name: "crashrecovery_java_defaults",
+ module_type: "java_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_crashrecovery_module",
+ ],
+ properties: [
+ "libs",
+ ],
+}
+
+crashrecovery_java_defaults {
+ name: "services_crashrecovery_stubs_conditionally",
+ soong_config_variables: {
+ release_crashrecovery_module: {
+ libs: ["service-crashrecovery.stubs.system_server"],
+ },
+ },
+}
+
+soong_config_module_type {
+ name: "ondeviceintelligence_module_java_defaults",
+ module_type: "java_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_ondevice_intelligence_module",
+ "release_ondevice_intelligence_platform",
+ ],
+ properties: [
+ "libs",
+ "srcs",
+ "static_libs",
+ ],
+}
+
+// Conditionally add ondeviceintelligence stubs library
+ondeviceintelligence_module_java_defaults {
+ name: "ondeviceintelligence_conditionally",
+ soong_config_variables: {
+ release_ondevice_intelligence_module: {
+ libs: ["service-ondeviceintelligence.stubs.system_server"],
+ },
+ release_ondevice_intelligence_platform: {
+ srcs: [":service-ondeviceintelligence-sources"],
+ static_libs: ["modules-utils-backgroundthread"],
+ },
+ },
+}
+
// merge all required services into one jar
// ============================================================
soong_config_module_type {
@@ -213,6 +264,8 @@ system_java_library {
defaults: [
"services_java_defaults",
"art_profile_java_defaults",
+ "services_crashrecovery_stubs_conditionally",
+ "ondeviceintelligence_conditionally",
],
installable: true,
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index ee3bbcaf711d..ad87ceaf6f38 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,10 +45,10 @@ flag {
}
flag {
- name: "compute_window_changes_on_a11y_v2"
+ name: "clear_shortcuts_when_activity_updates_to_service"
namespace: "accessibility"
- description: "Computes accessibility window changes in accessibility instead of wm package."
- bug: "322444245"
+ description: "When an a11y activity is updated to an a11y service, clears the associated shortcuts so that we don't skip the AccessibilityServiceWarning."
+ bug: "358092445"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -86,10 +86,17 @@ flag {
}
flag {
- name: "enable_hardware_shortcut_disables_warning"
- namespace: "accessibility"
- description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
- bug: "287065325"
+ name: "enable_hardware_shortcut_disables_warning"
+ namespace: "accessibility"
+ description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
+ bug: "287065325"
+}
+
+flag {
+ name: "enable_low_vision_hats"
+ namespace: "accessibility"
+ description: "Use HaTS for low vision feedback."
+ bug: "380346799"
}
flag {
@@ -114,10 +121,13 @@ flag {
}
flag {
- name: "enable_magnification_follows_mouse"
+ name: "enable_magnification_follows_mouse_bugfix"
namespace: "accessibility"
description: "Whether to enable mouse following for fullscreen magnification"
bug: "354696546"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -162,6 +172,16 @@ flag {
}
flag {
+ name: "magnification_enlarge_pointer_bugfix"
+ namespace: "accessibility"
+ description: "When fullscreen magnification is enabled, pointer icon is enlarged"
+ bug: "355734856"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "manager_avoid_receiver_timeout"
namespace: "accessibility"
description: "Register receivers on background handler so they have more time to complete"
@@ -172,6 +192,16 @@ flag {
}
flag {
+ name: "package_monitor_dedicated_thread"
+ namespace: "accessibility"
+ description: "Runs the A11yManagerService PackageMonitor on a dedicated thread"
+ bug: "348138695"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "manager_package_monitor_logic_fix"
namespace: "accessibility"
description: "Corrects the return values of the HandleForceStop function"
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index c234ee479a57..9ceca5d1dbfe 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -35,7 +35,6 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
-import static com.android.window.flags.Flags.deleteCaptureDisplay;
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
@@ -62,7 +61,6 @@ import android.graphics.ParcelableColorSpace;
import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerInternal;
import android.hardware.usb.UsbDevice;
import android.os.Binder;
import android.os.Build;
@@ -104,7 +102,6 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.magnification.MagnificationProcessor;
import com.android.server.wm.WindowManagerInternal;
@@ -165,16 +162,27 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
protected final AccessibilitySecurityPolicy mSecurityPolicy;
protected final AccessibilityTrace mTrace;
- // The attribution tag set by the service that is bound to this instance
+ /** The attribution tag set by the client that is bound to this instance */
protected String mAttributionTag;
protected int mDisplayTypes = DISPLAY_TYPE_DEFAULT;
- // The service that's bound to this instance. Whenever this value is non-null, this
- // object is registered as a death recipient
- IBinder mService;
+ /**
+ * Binder of the {@link #mClient}.
+ *
+ * <p>Whenever this value is non-null, it should be registered as a {@link
+ * IBinder.DeathRecipient}
+ */
+ @Nullable IBinder mClientBinder;
- IAccessibilityServiceClient mServiceInterface;
+ /**
+ * The accessibility client this class represents.
+ *
+ * <p>The client is in the application process, i.e., it's a client of system_server. Depending
+ * on the use case, the client can be an {@link AccessibilityService}, a {@code UiAutomation},
+ * etc.
+ */
+ @Nullable IAccessibilityServiceClient mClient;
int mEventTypes;
@@ -218,10 +226,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
int mGenericMotionEventSources;
int mObservedMotionEventSources;
- // the events pending events to be dispatched to this service
+ /** Pending events to be dispatched to the client */
final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
- /** Whether this service relies on its {@link AccessibilityCache} being up to date */
+ /** Whether the client relies on its {@link AccessibilityCache} being up to date */
boolean mUsesAccessibilityCache = false;
// Handler only for dispatching accessibility events since we use event
@@ -230,7 +238,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final SparseArray<IBinder> mOverlayWindowTokens = new SparseArray();
- // All the embedded accessibility overlays that have been added by this service.
+ /** All the embedded accessibility overlays that have been added by the client. */
private List<SurfaceControl> mOverlays = new ArrayList<>();
/** The timestamp of requesting to take screenshot in milliseconds */
@@ -274,7 +282,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
/**
* Called back to notify system that the client has changed
- * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
+ *
+ * @param serviceInfoChanged True if the client's AccessibilityServiceInfo changed.
*/
void onClientChangeLocked(boolean serviceInfoChanged);
@@ -360,21 +369,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mIPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
- @Override
- public void handleMessage(Message message) {
- final int eventType = message.what;
- AccessibilityEvent event = (AccessibilityEvent) message.obj;
- boolean serviceWantsEvent = message.arg1 != 0;
- notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
- }
- };
+ mEventDispatchHandler =
+ new Handler(mainHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ final int eventType = message.what;
+ AccessibilityEvent event = (AccessibilityEvent) message.obj;
+ boolean clientWantsEvent = message.arg1 != 0;
+ notifyAccessibilityEventInternal(eventType, event, clientWantsEvent);
+ }
+ };
setDynamicallyConfigurableProperties(accessibilityServiceInfo);
}
@Override
public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
- if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
+ if (!mRequestFilterKeyEvents || (mClient == null)) {
return false;
}
if((mAccessibilityServiceInfo.getCapabilities()
@@ -388,7 +398,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
if (svcClientTracingEnabled()) {
logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber);
}
- mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
+ mClient.onKeyEvent(keyEvent, sequenceNumber);
} catch (RemoteException e) {
return false;
}
@@ -470,7 +480,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
public boolean canReceiveEventsLocked() {
- return (mEventTypes != 0 && mService != null);
+ return (mEventTypes != 0 && mClientBinder != null);
}
@RequiresNoPermission
@@ -520,7 +530,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // If the XML manifest had data to configure the service its info
+ // If the XML manifest had data to configure the AccessibilityService, its info
// should be already set. In such a case update only the dynamically
// configurable properties.
boolean oldRequestIme = mRequestImeApis;
@@ -1500,68 +1510,31 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return;
}
final long identity = Binder.clearCallingIdentity();
- if (deleteCaptureDisplay()) {
- try {
- ScreenCapture.ScreenCaptureListener screenCaptureListener = new
- ScreenCapture.ScreenCaptureListener(
- (screenshotBuffer, result) -> {
- if (screenshotBuffer != null && result == 0) {
- sendScreenshotSuccess(screenshotBuffer, callback);
- } else {
- sendScreenshotFailure(
- AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
- callback);
- }
+ try {
+ ScreenCapture.ScreenCaptureListener screenCaptureListener = new
+ ScreenCapture.ScreenCaptureListener(
+ (screenshotBuffer, result) -> {
+ if (screenshotBuffer != null && result == 0) {
+ sendScreenshotSuccess(screenshotBuffer, callback);
+ } else {
+ sendScreenshotFailure(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+ callback);
}
- );
- mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener);
- } catch (Exception e) {
- sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
- callback);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } else {
- try {
- mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
- final ScreenshotHardwareBuffer screenshotBuffer = LocalServices
- .getService(DisplayManagerInternal.class).userScreenshot(displayId);
- if (screenshotBuffer != null) {
- sendScreenshotSuccess(screenshotBuffer, callback);
- } else {
- sendScreenshotFailure(
- AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
- callback);
}
- }, null).recycleOnUse());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ );
+ mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener);
+ } catch (Exception e) {
+ sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+ callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
private void sendScreenshotSuccess(ScreenshotHardwareBuffer screenshotBuffer,
RemoteCallback callback) {
- if (deleteCaptureDisplay()) {
- mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
- final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- final ParcelableColorSpace colorSpace =
- new ParcelableColorSpace(screenshotBuffer.getColorSpace());
-
- final Bundle payload = new Bundle();
- payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
- AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
- payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
- hardwareBuffer);
- payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
- payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
- SystemClock.uptimeMillis());
-
- // Send back the result.
- callback.sendResult(payload);
- hardwareBuffer.close();
- }, null).recycleOnUse());
- } else {
+ mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
final ParcelableColorSpace colorSpace =
new ParcelableColorSpace(screenshotBuffer.getColorSpace());
@@ -1578,7 +1551,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// Send back the result.
callback.sendResult(payload);
hardwareBuffer.close();
- }
+ }, null).recycleOnUse());
}
private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode,
@@ -1745,40 +1718,40 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
try {
// Clear the proxy in the other process so this
// IAccessibilityServiceConnection can be garbage collected.
- if (mServiceInterface != null) {
+ if (mClient != null) {
if (svcClientTracingEnabled()) {
logTraceSvcClient("init", "null, " + mId + ", null");
}
- mServiceInterface.init(null, mId, null);
+ mClient.init(null, mId, null);
}
} catch (RemoteException re) {
/* ignore */
}
- if (mService != null) {
+ if (mClientBinder != null) {
try {
- mService.unlinkToDeath(this, 0);
+ mClientBinder.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
Slog.e(LOG_TAG, "Failed unregistering death link");
}
- mService = null;
+ mClientBinder = null;
}
- mServiceInterface = null;
+ mClient = null;
mReceivedAccessibilityButtonCallbackSinceBind = false;
}
public boolean isConnectedLocked() {
- return (mService != null);
+ return (mClientBinder != null);
}
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
final int eventType = event.getEventType();
- final boolean serviceWantsEvent = wantsEventLocked(event);
+ final boolean clientWantsEvent = clientWantsEventLocked(event);
final boolean requiredForCacheConsistency = mUsesAccessibilityCache
&& ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
- if (!serviceWantsEvent && !requiredForCacheConsistency) {
+ if (!clientWantsEvent && !requiredForCacheConsistency) {
return;
}
@@ -1786,7 +1759,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return;
}
// Make a copy since during dispatch it is possible the event to
- // be modified to remove its source if the receiving service does
+ // be modified to remove its source if the receiving client does
// not have permission to access the window content.
AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
Message message;
@@ -1804,22 +1777,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// Send all messages, bypassing mPendingEvents
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
- message.arg1 = serviceWantsEvent ? 1 : 0;
+ message.arg1 = clientWantsEvent ? 1 : 0;
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}
/**
- * Determines if given event can be dispatched to a service based on the package of the
- * event source. Specifically, a service is notified if it is interested in events from the
- * package.
+ * Determines if given event can be dispatched to a client based on the package of the event
+ * source. Specifically, a client is notified if it is interested in events from the package.
*
* @param event The event.
- * @return True if the listener should be notified, false otherwise.
+ * @return True if the client should be notified, false otherwise.
*/
- private boolean wantsEventLocked(AccessibilityEvent event) {
-
+ private boolean clientWantsEventLocked(AccessibilityEvent event) {
if (!canReceiveEventsLocked()) {
return false;
}
@@ -1850,22 +1821,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
/**
- * Notifies an accessibility service client for a scheduled event given the event type.
+ * Notifies a client for a scheduled event given the event type.
*
* @param eventType The type of the event to dispatch.
*/
private void notifyAccessibilityEventInternal(
- int eventType,
- AccessibilityEvent event,
- boolean serviceWantsEvent) {
- IAccessibilityServiceClient listener;
+ int eventType, AccessibilityEvent event, boolean clientWantsEvent) {
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- listener = mServiceInterface;
+ client = mClient;
- // If the service died/was disabled while the message for dispatching
- // the accessibility event was propagating the listener may be null.
- if (listener == null) {
+ // If the client (in the application process) died/was disabled while the message for
+ // dispatching the accessibility event was propagating, "client" may be null.
+ if (client == null) {
return;
}
@@ -1880,7 +1849,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
// which posts a message for dispatching an event and stores the event
// in mPendingEvents.
- // 2) The message is pulled from the queue by the handler on the service
+ // 2) The message is pulled from the queue by the handler on the client
// thread and this method is just about to acquire the lock.
// 3) Another binder thread acquires the lock in notifyAccessibilityEvent
// 4) notifyAccessibilityEvent recycles the event that this method was about
@@ -1888,7 +1857,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// 5) This method grabs the new event, processes it, and removes it from
// mPendingEvents
// 6) The second message dispatched in (4) arrives, but the event has been
- // remvoved in (5).
+ // removed in (5).
event = mPendingEvents.get(eventType);
if (event == null) {
return;
@@ -1905,14 +1874,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
try {
if (svcClientTracingEnabled()) {
- logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent);
+ logTraceSvcClient("onAccessibilityEvent", event + ";" + clientWantsEvent);
}
- listener.onAccessibilityEvent(event, serviceWantsEvent);
+ client.onAccessibilityEvent(event, clientWantsEvent);
if (DEBUG) {
- Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+ Slog.i(LOG_TAG, "Event " + event + " sent to " + client);
}
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + client, re);
} finally {
event.recycle();
}
@@ -1990,122 +1959,126 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
}
-
/**
- * Called by the invocation handler to notify the service that the
- * state of magnification has changed.
+ * Called by the invocation handler to notify the client that the state of magnification has
+ * changed.
*/
- private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
- @NonNull MagnificationConfig config) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ private void notifyMagnificationChangedInternal(
+ int displayId, @NonNull Region region, @NonNull MagnificationConfig config) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", "
+ config.toString());
}
- listener.onMagnificationChanged(displayId, region, config);
+ client.onMagnificationChanged(displayId, region, config);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+ Slog.e(LOG_TAG, "Error sending magnification changes to " + mClientBinder, re);
}
}
}
/**
- * Called by the invocation handler to notify the service that the state of the soft
- * keyboard show mode has changed.
+ * Called by the invocation handler to notify the client that the state of the soft keyboard
+ * show mode has changed.
*/
private void notifySoftKeyboardShowModeChangedInternal(int showState) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState));
}
- listener.onSoftKeyboardShowModeChanged(showState);
+ client.onSoftKeyboardShowModeChanged(showState);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error sending soft keyboard show mode changes to " + mClientBinder,
re);
}
}
}
private void notifyAccessibilityButtonClickedInternal(int displayId) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId));
}
- listener.onAccessibilityButtonClicked(displayId);
+ client.onAccessibilityButtonClicked(displayId);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+ Slog.e(LOG_TAG, "Error sending accessibility button click to " + mClientBinder, re);
}
}
}
private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
- // Only notify the service if it's not been notified or the state has changed
+ // Only notify the client if it's not been notified or the state has changed
if (mReceivedAccessibilityButtonCallbackSinceBind
&& (mLastAccessibilityButtonCallbackState == available)) {
return;
}
mReceivedAccessibilityButtonCallbackSinceBind = true;
mLastAccessibilityButtonCallbackState = available;
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onAccessibilityButtonAvailabilityChanged",
String.valueOf(available));
}
- listener.onAccessibilityButtonAvailabilityChanged(available);
+ client.onAccessibilityButtonAvailabilityChanged(available);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error sending accessibility button availability change to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error sending accessibility button availability change to "
+ + mClientBinder,
re);
}
}
}
private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onGesture", gestureInfo.toString());
}
- listener.onGesture(gestureInfo);
+ client.onGesture(gestureInfo);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo
- + " to " + mService, re);
+ Slog.e(
+ LOG_TAG,
+ "Error during sending gesture " + gestureInfo + " to " + mClientBinder,
+ re);
}
}
}
private void notifySystemActionsChangedInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onSystemActionsChanged", "");
}
- listener.onSystemActionsChanged();
+ client.onSystemActionsChanged();
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
- re);
+ Slog.e(LOG_TAG, "Error sending system actions change to " + mClientBinder, re);
}
}
}
private void notifyClearAccessibilityCacheInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("clearAccessibilityCache", "");
}
- listener.clearAccessibilityCache();
+ client.clearAccessibilityCache();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+ " to be cleared.", re);
@@ -2118,70 +2091,66 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session,
boolean enabled) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null && session != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null && session != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("createImeSession", "");
}
- listener.setImeSessionEnabled(session, enabled);
+ client.setImeSessionEnabled(session, enabled);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
+ Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
}
}
}
private void bindInputInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("bindInput", "");
}
- listener.bindInput();
+ client.bindInput();
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error binding input to " + mService, re);
+ Slog.e(LOG_TAG, "Error binding input to " + mClientBinder, re);
}
}
}
private void unbindInputInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("unbindInput", "");
}
- listener.unbindInput();
+ client.unbindInput();
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error unbinding input to " + mService, re);
+ Slog.e(LOG_TAG, "Error unbinding input to " + mClientBinder, re);
}
}
}
private void startInputInternal(IRemoteAccessibilityInputConnection connection,
EditorInfo editorInfo, boolean restarting) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("startInput", "editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
- listener.startInput(connection, editorInfo, restarting);
+ client.startInput(connection, editorInfo, restarting);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error starting input to " + mService, re);
+ Slog.e(LOG_TAG, "Error starting input to " + mClientBinder, re);
}
}
}
- protected IAccessibilityServiceClient getServiceInterfaceSafely() {
+ protected IAccessibilityServiceClient getClientSafely() {
synchronized (mLock) {
- return mServiceInterface;
+ return mClient;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 617cca9d3075..e1b6c9c5aa42 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -27,6 +27,8 @@ import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
+import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -44,11 +46,14 @@ import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.Nullable;
+
import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
+import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.MouseEventHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
@@ -187,6 +192,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private final AccessibilityManagerService mAms;
+ private final InputManager mInputManager;
+
private final SparseArray<EventStreamTransformation> mEventHandler;
private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
@@ -228,6 +235,47 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
private MotionEvent mLastActiveDeviceMotionEvent = null;
+ private boolean mKeyGestureEventHandlerInstalled = false;
+ private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+ new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(
+ @NonNull KeyGestureEvent event,
+ @Nullable IBinder focusedToken) {
+ final boolean complete =
+ event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ final int gestureType = event.getKeyGestureType();
+ final int displayId = isDisplayIdValid(event.getDisplayId())
+ ? event.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+ if (complete) {
+ mAms.getMagnificationController().scaleMagnificationByStep(
+ displayId, MagnificationController.ZOOM_DIRECTION_IN);
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+ if (complete) {
+ mAms.getMagnificationController().scaleMagnificationByStep(
+ displayId, MagnificationController.ZOOM_DIRECTION_OUT);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ return switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true;
+ default -> false;
+ };
+ }
+ };
+
private static MotionEvent cancelMotion(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
@@ -287,6 +335,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mContext = context;
mAms = service;
mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mInputManager = context.getSystemService(InputManager.class);
mEventHandler = eventHandler;
}
@@ -360,10 +409,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
final int eventSource = event.getSource();
final int displayId = event.getDisplayId();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
- if (!Flags.doNotResetKeyEventState()) {
- state.reset();
- clearEventStreamHandler(displayId, eventSource);
- }
if (DEBUG) {
Slog.d(TAG, "Not processing event " + event);
}
@@ -723,6 +768,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
createMagnificationGestureHandler(displayId, displayContext);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+
+ if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()
+ && !mKeyGestureEventHandlerInstalled) {
+ mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ mKeyGestureEventHandlerInstalled = true;
+ }
}
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
@@ -842,6 +893,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mMouseKeysInterceptor.onDestroy();
mMouseKeysInterceptor = null;
}
+
+ if (mKeyGestureEventHandlerInstalled) {
+ mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler);
+ mKeyGestureEventHandlerInstalled = false;
+ }
}
private MagnificationGestureHandler createMagnificationGestureHandler(
@@ -1120,18 +1176,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
- if (Flags.alwaysAllowObservingTouchEvents()) {
- final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
- if (isTouchEvent && !canShareGenericTouchEvent()) {
- return false;
- }
- final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0;
- }
- // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
- // touch exploration.
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
- && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
+ if (isTouchEvent && !canShareGenericTouchEvent()) {
return false;
}
final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
@@ -1139,21 +1185,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) {
- if (Flags.alwaysAllowObservingTouchEvents()) {
- final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
- }
- // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
- // touch exploration.
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
- && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
- return false;
- }
final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedGenericMotionEventSources
- & mCombinedMotionEventObservedSources
- & eventSourceWithoutClass)
- != 0;
+ return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
}
private boolean canShareGenericTouchEvent() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 49f15e46894d..5c1ad74fac93 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -42,9 +42,11 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATIN
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -52,8 +54,10 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -110,12 +114,15 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -336,6 +343,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private AlertDialog mEnableTouchExplorationDialog;
+ private final InputManager mInputManager;
+
private AccessibilityInputFilter mInputFilter;
private boolean mHasInputFilter;
@@ -501,6 +510,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+ new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(
+ @NonNull KeyGestureEvent event,
+ @Nullable IBinder focusedToken) {
+ return AccessibilityManagerService.this.handleKeyGestureEvent(event);
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ return switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true;
+ default -> false;
+ };
+ }
+ };
+
@VisibleForTesting
AccessibilityManagerService(
Context context,
@@ -540,6 +568,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUmi = LocalServices.getService(UserManagerInternal.class);
// TODO(b/255426725): not used on tests
mVisibleBgUserIds = null;
+ mInputManager = context.getSystemService(InputManager.class);
init();
}
@@ -581,6 +610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
mUmi = LocalServices.getService(UserManagerInternal.class);
+ mInputManager = context.getSystemService(InputManager.class);
if (UserManager.isVisibleBackgroundUsersEnabled()) {
mVisibleBgUserIds = new SparseBooleanArray();
@@ -597,6 +627,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
mContext.getContentResolver());
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ }
disableAccessibilityMenuToMigrateIfNeeded();
}
@@ -638,6 +671,79 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mIsAccessibilityButtonShown;
}
+ @VisibleForTesting
+ boolean handleKeyGestureEvent(KeyGestureEvent event) {
+ final boolean complete =
+ event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ final int gestureType = event.getKeyGestureType();
+ if (!complete) {
+ return false;
+ }
+
+ String targetName;
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
+ targetName = MAGNIFICATION_CONTROLLER_NAME;
+ break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
+ targetName = mContext.getString(R.string.config_defaultSelectToSpeakService);
+ if (targetName.isEmpty()) {
+ return false;
+ }
+
+ final ComponentName targetServiceComponent = TextUtils.isEmpty(targetName)
+ ? null : ComponentName.unflattenFromString(targetName);
+ AccessibilityServiceInfo accessibilityServiceInfo;
+ synchronized (mLock) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ accessibilityServiceInfo =
+ userState.getInstalledServiceInfoLocked(targetServiceComponent);
+ }
+ if (accessibilityServiceInfo == null) {
+ return false;
+ }
+
+ // Skip enabling if a warning dialog is required for the feature.
+ // TODO(b/377752960): Explore better options to instead show the warning dialog
+ // in this scenario.
+ if (isAccessibilityServiceWarningRequired(accessibilityServiceInfo)) {
+ Slog.w(LOG_TAG,
+ "Accessibility warning is required before this service can be "
+ + "activated automatically via KEY_GESTURE shortcut.");
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ List<String> shortcutTargets = getAccessibilityShortcutTargets(
+ KEY_GESTURE);
+ if (!shortcutTargets.contains(targetName)) {
+ int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ }
+ // TODO(b/377752960): Add dialog to confirm enabling the service and to
+ // activate the first time.
+ enableShortcutForTargets(true, UserShortcutType.KEY_GESTURE,
+ List.of(targetName), userId);
+
+ // Do not perform action on first press since it was just registered. Eventually,
+ // this will be a separate dialog that appears that requires the user to confirm
+ // which will resolve this race condition. For now, just require two presses the
+ // first time it is activated.
+ return true;
+ }
+
+ final int displayId = event.getDisplayId() != INVALID_DISPLAY
+ ? event.getDisplayId() : getLastNonProxyTopFocusedDisplayId();
+ performAccessibilityShortcutInternal(displayId, KEY_GESTURE, targetName);
+
+ return true;
+ }
+
@Override
public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
int windowId) {
@@ -901,7 +1007,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void registerBroadcastReceivers() {
// package changes
mPackageMonitor = new ManagerPackageMonitor(this);
- mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+ final Looper packageMonitorLooper;
+ if (Flags.packageMonitorDedicatedThread()) {
+ // Use a dedicated thread because the default BackgroundThread used by PackageMonitor
+ // is shared by other components and can get busy, causing a delay and eventual ANR when
+ // responding to broadcasts sent to this PackageMonitor.
+ HandlerThread packageMonitorThread = new HandlerThread(LOG_TAG + " PackageMonitor",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ packageMonitorThread.start();
+ packageMonitorLooper = packageMonitorThread.getLooper();
+ } else {
+ packageMonitorLooper = null;
+ }
+ mPackageMonitor.register(mContext, packageMonitorLooper, UserHandle.ALL, true);
// user change and unlock
IntentFilter intentFilter = new IntentFilter();
@@ -910,8 +1028,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
- Handler receiverHandler =
- Flags.managerAvoidReceiverTimeout() ? BackgroundThread.getHandler() : null;
+ Handler receiverHandler = BackgroundThread.getHandler();
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -953,8 +1070,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
newValue, restoredFromSdk);
}
}
+ // Currently in SUW, the user can't see gesture shortcut option as the
+ // navigation system is set to button navigation. We'll rely on the
+ // SettingsBackupAgent to restore the settings since we don't
+ // need to merge an empty gesture target.
case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS,
Settings.Secure.ACCESSIBILITY_QS_TARGETS,
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE ->
restoreShortcutTargets(newValue,
@@ -1210,14 +1330,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
int displayId = event.getDisplayId();
final int windowId = event.getWindowId();
if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
- && displayId == Display.INVALID_DISPLAY) {
+ && displayId == INVALID_DISPLAY) {
displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
resolvedUserId, windowId);
event.setDisplayId(displayId);
}
synchronized (mLock) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && displayId != Display.INVALID_DISPLAY
+ && displayId != INVALID_DISPLAY
&& mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
shouldComputeWindows = true;
}
@@ -1435,8 +1555,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
interfacesToInterrupt = new ArrayList<>(services.size());
for (int i = 0; i < services.size(); i++) {
AccessibilityServiceConnection service = services.get(i);
- IBinder a11yServiceBinder = service.mService;
- IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+ IBinder a11yServiceBinder = service.mClientBinder;
+ IAccessibilityServiceClient a11yServiceInterface = service.mClient;
if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
interfacesToInterrupt.add(a11yServiceInterface);
}
@@ -2139,10 +2259,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
- if (shortcutType == HARDWARE
- && !android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
- return;
- }
synchronized (mLock) {
final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
@@ -2151,8 +2267,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext, shortcutType, userState.mUserId))
: userState.getShortcutTargetsLocked(shortcutType);
- if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore()
- && shortcutType == HARDWARE) {
+ if (shortcutType == HARDWARE) {
final String defaultService =
mContext.getString(R.string.config_defaultAccessibilityService);
final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService)
@@ -2513,6 +2628,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
+ if (Flags.clearShortcutsWhenActivityUpdatesToService()) {
+ List<String> componentNames = userState.mInstalledShortcuts.stream()
+ .filter(a11yActivity ->
+ !parsedAccessibilityShortcutInfos.contains(a11yActivity))
+ .map(a11yActivity -> a11yActivity.getComponentName().flattenToString())
+ .toList();
+ if (!componentNames.isEmpty()) {
+ enableShortcutsForTargets(
+ /* enable= */ false, UserShortcutType.ALL,
+ componentNames, userState.mUserId);
+ }
+ }
+
userState.mInstalledShortcuts.clear();
userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
userState.updateTileServiceMapForAccessibilityActivityLocked();
@@ -2799,27 +2927,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final String builderValue = builder.toString();
final String settingValue = TextUtils.isEmpty(builderValue)
? defaultEmptyString : builderValue;
- if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
- final String currentValue = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), settingName, userId);
- if (Objects.equals(settingValue, currentValue)) {
- // This logic exists to fix a bug where AccessibilityManagerService was writing
- // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
- // during setup, due to a race condition in package scanning making A11yMS think
- // that the default service was not installed.
- //
- // Writing `null` was implicitly causing that Setting to have the default
- // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
- // Setting altogether.
- //
- // The "quick fix" here is to not write `null` if the existing value is already
- // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
- // that allows override-by-restore, but the full repercussions of using that here
- // have not yet been evaluated.
- // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
- // "overridable by restore" when writing secure settings.
- return;
- }
+ final String currentValue = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), settingName, userId);
+ if (Objects.equals(settingValue, currentValue)) {
+ // This logic exists to fix a bug where AccessibilityManagerService was writing
+ // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
+ // during setup, due to a race condition in package scanning making A11yMS think
+ // that the default service was not installed.
+ //
+ // Writing `null` was implicitly causing that Setting to have the default
+ // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
+ // Setting altogether.
+ //
+ // The "quick fix" here is to not write `null` if the existing value is already
+ // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
+ // that allows override-by-restore, but the full repercussions of using that here
+ // have not yet been evaluated.
+ // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
+ // "overridable by restore" when writing secure settings.
+ return;
}
final long identity = Binder.clearCallingIdentity();
try {
@@ -3230,6 +3356,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
updateAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
updateAccessibilityShortcutTargetsLocked(userState, GESTURE);
updateAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
+ updateAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE);
// Update the capabilities before the mode because we will check the current mode is
// invalid or not..
updateMagnificationCapabilitiesSettingsChangeLocked(userState);
@@ -3360,6 +3487,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, GESTURE);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE);
somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
@@ -3664,13 +3792,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|| (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
&& userState.isMagnificationTwoFingerTripleTapEnabledLocked()));
- final boolean createConnectionForCurrentCapability =
- com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
- || (userState.getMagnificationCapabilitiesLocked()
- != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-
- final boolean connect = (shortcutEnabled && createConnectionForCurrentCapability)
- || userHasMagnificationServicesLocked(userState);
+ final boolean connect = shortcutEnabled || userHasMagnificationServicesLocked(userState);
getMagnificationConnectionManager().requestConnection(connect);
}
@@ -3871,6 +3993,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.getShortcutTargetsLocked(HARDWARE);
final Set<String> qsShortcutTargets =
userState.getShortcutTargetsLocked(QUICK_SETTINGS);
+ final Set<String> shortcutTargets = userState.getShortcutTargetsLocked(ALL);
userState.mEnabledServices.forEach(componentName -> {
if (packageName != null && componentName != null
&& !packageName.equals(componentName.getPackageName())) {
@@ -3891,7 +4014,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (TextUtils.isEmpty(serviceName)) {
return;
}
- if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
+ if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ if (doesShortcutTargetsStringContain(shortcutTargets, serviceName)) {
+ return;
+ }
+ } else if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
|| doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)
|| doesShortcutTargetsStringContain(qsShortcutTargets, serviceName)) {
return;
@@ -3936,6 +4063,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
shortcutTypes.add(GESTURE);
}
+ shortcutTypes.add(KEY_GESTURE);
final ComponentName serviceName = service.getComponentName();
for (Integer shortcutType: shortcutTypes) {
@@ -4046,13 +4174,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
private void performAccessibilityShortcutInternal(int displayId,
@UserShortcutType int shortcutType, @Nullable String targetName) {
- final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
+ final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(
+ shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
return;
}
// In case the caller specified a target name
- if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) {
+ if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets,
+ targetName)) {
Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
targetName = null;
}
@@ -4274,6 +4404,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
+ if (shortcutType == UserShortcutType.KEY_GESTURE
+ && !enableTalkbackAndMagnifierKeyGestures()) {
+ Slog.w(LOG_TAG,
+ "KEY_GESTURE type shortcuts are disabled by feature flag");
+ return;
+ }
+
final String shortcutTypeSettingKey = ShortcutUtils.convertToKey(shortcutType);
if (shortcutType == UserShortcutType.TRIPLETAP
|| shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
@@ -4355,13 +4492,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
if (shortcutType == HARDWARE) {
skipVolumeShortcutDialogTimeoutRestriction(userId);
- if (com.android.server.accessibility.Flags.enableHardwareShortcutDisablesWarning()) {
- persistIntToSetting(
- userId,
- Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- AccessibilityShortcutController.DialogStatus.SHOWN
- );
- }
+ persistIntToSetting(
+ userId,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.SHOWN
+ );
} else if (shortcutType == SOFTWARE) {
// Update the A11y FAB size to large when the Magnification shortcut is
// enabled and the user hasn't changed the floating button size
@@ -4706,8 +4841,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
getMagnificationConnectionManager().setConnection(connection);
- if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
- && connection == null
+ if (connection == null
&& mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
// Since the connection does not exist, the system ui cannot provide the border
// implementation for fullscreen magnification. So we call reset to deactivate the
@@ -4968,9 +5102,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
&& android.security.Flags.extendEcmToAllSettings()) {
try {
- return !mContext.getSystemService(EnhancedConfirmationManager.class)
- .isRestricted(packageName,
- AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ final EnhancedConfirmationManager userContextEcm =
+ mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0)
+ .getSystemService(EnhancedConfirmationManager.class);
+ if (userContextEcm != null) {
+ return !userContextEcm.isRestricted(packageName,
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ }
+ return false;
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
return false;
@@ -5034,6 +5173,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@EnforcePermission(MANAGE_ACCESSIBILITY)
public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
isAccessibilityServiceWarningRequired_enforcePermission();
+ if (info == null) {
+ Log.e(LOG_TAG, "Called isAccessibilityServiceWarningRequired with null service info");
+ return true;
+ }
+
final ComponentName componentName = info.getComponentName();
// Warning is not required if the service is already enabled.
@@ -5641,6 +5785,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mAccessibilityGestureTargetsUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+ private final Uri mAccessibilityKeyGestureTargetsUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS);
+
private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS);
@@ -5705,6 +5852,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
contentResolver.registerContentObserver(
mAccessibilityGestureTargetsUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
+ mAccessibilityKeyGestureTargetsUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mUserInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
@@ -5786,6 +5935,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (readAccessibilityShortcutTargetsLocked(userState, GESTURE)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityKeyGestureTargetsUri.equals(uri)) {
+ if (readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE)) {
+ onUserStateChangedLocked(userState);
+ }
} else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
|| mUserInteractiveUiTimeoutUri.equals(uri)) {
readUserRecommendedUiTimeoutSettingsLocked(userState);
@@ -6383,8 +6536,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// Only continue setting up the packages if the service has been initialized.
// See: b/340927041
- if (Flags.skipPackageChangeBeforeUserSwitch()
- && !mManagerService.isServiceInitializedLocked()) {
+ if (!mManagerService.isServiceInitializedLocked()) {
Slog.w(LOG_TAG,
"onSomePackagesChanged: service not initialized, skip the callback.");
return;
@@ -6492,28 +6644,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId);
- if (Flags.managerPackageMonitorLogicFix()) {
- if (!doit) {
- // if we're not handling the stop here, then we only need to know
- // if any of the force-stopped packages are currently enabled.
- return userState.mEnabledServices.stream().anyMatch(
- (comp) -> Arrays.stream(packages).anyMatch(
- (pkg) -> pkg.equals(comp.getPackageName()))
- );
- } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
- mManagerService.onUserStateChangedLocked(userState);
- }
- return false;
- } else {
- // this old logic did not properly indicate when base packageMonitor's routine
- // should handle stopping the package.
- if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
- mManagerService.onUserStateChangedLocked(userState);
- return false;
- } else {
- return true;
- }
+ if (!doit) {
+ // if we're not handling the stop here, then we only need to know
+ // if any of the force-stopped packages are currently enabled.
+ return userState.mEnabledServices.stream().anyMatch(
+ (comp) -> Arrays.stream(packages).anyMatch(
+ (pkg) -> pkg.equals(comp.getPackageName()))
+ );
+ } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
+ mManagerService.onUserStateChangedLocked(userState);
}
+ return false;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index f45fa921c4a2..5ae077363c88 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -405,10 +405,9 @@ public class AccessibilitySecurityPolicy {
* @throws SecurityException if the input method is not in the same package as the service.
*/
@AccessibilityService.SoftKeyboardController.EnableImeResult
- int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service)
- throws SecurityException {
+ int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service,
+ int callingUserId) throws SecurityException {
final String servicePackageName = service.getComponentName().getPackageName();
- final int callingUserId = UserHandle.getCallingUserId();
InputMethodInfo inputMethodInfo = null;
List<InputMethodInfo> inputMethodInfoList =
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 786d167af5de..a3fe9ec5ea22 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -166,8 +166,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
- if (mService == null && mContext.bindServiceAsUser(
- mIntent, this, flags, new UserHandle(userState.mUserId))) {
+ if (mClientBinder == null
+ && mContext.bindServiceAsUser(
+ mIntent, this, flags, new UserHandle(userState.mUserId))) {
userState.getBindingServicesLocked().add(mComponentName);
}
} finally {
@@ -227,20 +228,20 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
addWindowTokensForAllDisplays();
}
synchronized (mLock) {
- if (mService != service) {
- if (mService != null) {
- mService.unlinkToDeath(this, 0);
+ if (mClientBinder != service) {
+ if (mClientBinder != null) {
+ mClientBinder.unlinkToDeath(this, 0);
}
- mService = service;
+ mClientBinder = service;
try {
- mService.linkToDeath(this, 0);
+ mClientBinder.linkToDeath(this, 0);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed registering death link");
binderDied();
return;
}
}
- mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+ mClient = IAccessibilityServiceClient.Stub.asInterface(service);
if (userState == null) return;
userState.addServiceLocked(this);
mSystemSupport.onClientChangeLocked(false);
@@ -261,7 +262,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
}
private void initializeService() {
- IAccessibilityServiceClient serviceInterface = null;
+ IAccessibilityServiceClient client = null;
synchronized (mLock) {
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
@@ -272,18 +273,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
bindingServices.remove(mComponentName);
crashedServices.remove(mComponentName);
mAccessibilityServiceInfo.crashed = false;
- serviceInterface = mServiceInterface;
+ client = mClient;
}
// There's a chance that service is removed from enabled_accessibility_services setting
// key, but skip unbinding because of it's in binding state. Unbinds it if it's
// not in enabled service list.
- if (serviceInterface != null
- && !userState.getEnabledServicesLocked().contains(mComponentName)) {
+ if (client != null && !userState.getEnabledServicesLocked().contains(mComponentName)) {
mSystemSupport.onClientChangeLocked(false);
return;
}
}
- if (serviceInterface == null) {
+ if (client == null) {
binderDied();
return;
}
@@ -292,10 +292,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
logTraceSvcClient("init",
this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
}
- serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ client.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
} catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error while setting connection for service: "
- + serviceInterface, re);
+ Slog.w(LOG_TAG, "Error while setting connection for service: " + client, re);
binderDied();
}
}
@@ -411,9 +410,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
final @AccessibilityService.SoftKeyboardController.EnableImeResult int checkResult;
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this);
- }
+ checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this, callingUserId);
if (checkResult != ENABLE_IME_SUCCESS) {
return checkResult;
}
@@ -496,7 +493,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public boolean isCapturingFingerprintGestures() {
- return (mServiceInterface != null)
+ return (mClient != null)
&& mSecurityPolicy.canCaptureFingerprintGestures(this)
&& mCaptureFingerprintGestures;
}
@@ -506,17 +503,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (!isCapturingFingerprintGestures()) {
return;
}
- IAccessibilityServiceClient serviceInterface;
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- serviceInterface = mServiceInterface;
+ client = mClient;
}
- if (serviceInterface != null) {
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient(
"onFingerprintCapturingGesturesChanged", String.valueOf(active));
}
- mServiceInterface.onFingerprintCapturingGesturesChanged(active);
+ mClient.onFingerprintCapturingGesturesChanged(active);
} catch (RemoteException e) {
}
}
@@ -527,16 +524,16 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (!isCapturingFingerprintGestures()) {
return;
}
- IAccessibilityServiceClient serviceInterface;
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- serviceInterface = mServiceInterface;
+ client = mClient;
}
- if (serviceInterface != null) {
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture));
}
- mServiceInterface.onFingerprintGesture(gesture);
+ mClient.onFingerprintGesture(gesture);
} catch (RemoteException e) {
}
}
@@ -546,7 +543,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
synchronized (mLock) {
- if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
+ if (mClient != null && mSecurityPolicy.canPerformGestures(this)) {
final long identity = Binder.clearCallingIdentity();
try {
MotionEventInjector motionEventInjector =
@@ -557,16 +554,18 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (motionEventInjector != null
&& mWindowManagerService.isTouchOrFaketouchDevice()) {
motionEventInjector.injectEvents(
- gestureSteps.getList(), mServiceInterface, sequence, displayId);
+ gestureSteps.getList(), mClient, sequence, displayId);
} else {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onPerformGestureResult", sequence + ", false");
}
- mServiceInterface.onPerformGestureResult(sequence, false);
+ mClient.onPerformGestureResult(sequence, false);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event injection failure to "
- + mServiceInterface, re);
+ Slog.e(
+ LOG_TAG,
+ "Error sending motion event injection failure to " + mClient,
+ re);
}
}
} finally {
@@ -631,48 +630,47 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
protected void createImeSessionInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("createImeSession", "");
}
AccessibilityInputMethodSessionCallback
callback = new AccessibilityInputMethodSessionCallback(mUserId);
- listener.createImeSession(callback);
+ client.createImeSession(callback);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
+ Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
}
}
}
private void notifyMotionEventInternal(MotionEvent event) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (mTrace.isA11yTracingEnabled()) {
logTraceSvcClient(".onMotionEvent ",
event.toString());
}
- listener.onMotionEvent(event);
+ client.onMotionEvent(event);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+ Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
}
}
}
private void notifyTouchStateInternal(int displayId, int state) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (mTrace.isA11yTracingEnabled()) {
logTraceSvcClient(".onTouchStateChanged ",
TouchInteractionController.stateToString(state));
}
- listener.onTouchStateChanged(displayId, state);
+ client.onTouchStateChanged(displayId, state);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+ Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0bf7ec001d4d..8b3e63d0dc5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -29,6 +29,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -106,21 +107,17 @@ class AccessibilityUserState {
final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
- private final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
-
- private final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
- private final ArraySet<String> mAccessibilityGestureTargets = new ArraySet<>();
- private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
+ private final HashMap<Integer, ArraySet<String>> mShortcutTargets = new HashMap<>();
/**
- * The QuickSettings tiles in the QS Panel. This can be different from
- * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
+ * The QuickSettings tiles in the QS Panel. This can be different from the QS targets in
+ * {@link #mShortcutTargets} in that {@link #mA11yTilesInQsPanel} stores the
* TileService's or the a11y framework tile component names (e.g.
* {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
* A11y Feature's component names.
* <p/>
* In addition, {@link #mA11yTilesInQsPanel} stores what's on the QS Panel, whereas
- * {@link #mAccessibilityQsTargets} stores the targets that configured qs as their shortcut and
+ * {@link #mShortcutTargets} stores the targets that configured qs as their shortcut and
* also grant full device control permission.
*/
private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
@@ -208,6 +205,12 @@ class AccessibilityUserState {
mSupportWindowMagnification = mContext.getResources().getBoolean(
R.bool.config_magnification_area) && mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WINDOW_MAGNIFICATION);
+
+ mShortcutTargets.put(HARDWARE, new ArraySet<>());
+ mShortcutTargets.put(SOFTWARE, new ArraySet<>());
+ mShortcutTargets.put(GESTURE, new ArraySet<>());
+ mShortcutTargets.put(QUICK_SETTINGS, new ArraySet<>());
+ mShortcutTargets.put(KEY_GESTURE, new ArraySet<>());
}
boolean isHandlingAccessibilityEventsLocked() {
@@ -233,10 +236,7 @@ class AccessibilityUserState {
// Clear state persisted in settings.
mEnabledServices.clear();
mTouchExplorationGrantedServices.clear();
- mAccessibilityShortcutKeyTargets.clear();
- mAccessibilityButtonTargets.clear();
- mAccessibilityGestureTargets.clear();
- mAccessibilityQsTargets.clear();
+ mShortcutTargets.forEach((type, targets) -> targets.clear());
mA11yTilesInQsPanel.clear();
mTargetAssignedToAccessibilityButton = null;
mIsTouchExplorationEnabled = false;
@@ -541,7 +541,7 @@ class AccessibilityUserState {
private void dumpShortcutTargets(
PrintWriter pw, @UserShortcutType int shortcutType, String name) {
pw.append(" ").append(name).append(":{");
- ArraySet<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+ ArraySet<String> targets = getShortcutTargetsLocked(shortcutType);
int size = targets.size();
for (int i = 0; i < size; i++) {
if (i > 0) {
@@ -712,7 +712,7 @@ class AccessibilityUserState {
*/
public boolean isShortcutMagnificationEnabledLocked() {
for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
- if (getShortcutTargetsInternalLocked(shortcutType)
+ if (getShortcutTargetsLocked(shortcutType)
.contains(MAGNIFICATION_CONTROLLER_NAME)) {
return true;
}
@@ -788,43 +788,29 @@ class AccessibilityUserState {
}
/**
- * Disable both shortcuts' magnification function.
- */
- public void disableShortcutMagnificationLocked() {
- mAccessibilityShortcutKeyTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
- mAccessibilityButtonTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
- }
-
- /**
* Returns a set which contains the flattened component names and the system class names
- * assigned to the given shortcut. The set is a defensive copy. To apply any changes to the set,
- * use {@link #updateShortcutTargetsLocked(Set, int)}
+ * assigned to the given shortcut. <strong>The set is a defensive copy.</strong>
+ * To apply any changes to the set, use {@link #updateShortcutTargetsLocked(Set, int)}
*
- * @param shortcutType The shortcut type.
+ * @param shortcutTypes The shortcut type or types (in bitmask format).
* @return The array set of the strings
*/
- public ArraySet<String> getShortcutTargetsLocked(@UserShortcutType int shortcutType) {
- return new ArraySet<>(getShortcutTargetsInternalLocked(shortcutType));
- }
-
- private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) {
- if (shortcutType == HARDWARE) {
- return mAccessibilityShortcutKeyTargets;
- } else if (shortcutType == SOFTWARE) {
- return mAccessibilityButtonTargets;
- } else if (shortcutType == GESTURE) {
- return mAccessibilityGestureTargets;
- } else if (shortcutType == QUICK_SETTINGS) {
- return mAccessibilityQsTargets;
- } else if ((shortcutType == TRIPLETAP
- && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
- shortcutType == TWOFINGER_DOUBLETAP
- && isMagnificationTwoFingerTripleTapEnabledLocked())) {
- ArraySet<String> targets = new ArraySet<>();
- targets.add(MAGNIFICATION_CONTROLLER_NAME);
- return targets;
+ public ArraySet<String> getShortcutTargetsLocked(int shortcutTypes) {
+ ArraySet<String> targets = new ArraySet<>();
+ for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+ if ((shortcutTypes & shortcutType) != shortcutType) {
+ continue;
+ }
+ if ((shortcutType == TRIPLETAP
+ && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
+ shortcutType == TWOFINGER_DOUBLETAP
+ && isMagnificationTwoFingerTripleTapEnabledLocked())) {
+ targets.add(MAGNIFICATION_CONTROLLER_NAME);
+ } else if (mShortcutTargets.containsKey(shortcutType)) {
+ targets.addAll(mShortcutTargets.get(shortcutType));
+ }
}
- return new ArraySet<>();
+ return targets;
}
/**
@@ -843,8 +829,10 @@ class AccessibilityUserState {
if ((shortcutType & mask) != 0) {
throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets.");
}
-
- final Set<String> currentTargets = getShortcutTargetsInternalLocked(shortcutType);
+ if (!mShortcutTargets.containsKey(shortcutType)) {
+ mShortcutTargets.put(shortcutType, new ArraySet<>());
+ }
+ ArraySet<String> currentTargets = mShortcutTargets.get(shortcutType);
if (newTargets.equals(currentTargets)) {
return false;
}
@@ -904,7 +892,7 @@ class AccessibilityUserState {
}
// getting internal set lets us directly modify targets, as it's not a copy.
- Set<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+ Set<String> targets = mShortcutTargets.get(shortcutType);
return targets.removeIf(name -> {
ComponentName componentName;
if (name == null
@@ -1169,13 +1157,6 @@ class AccessibilityUserState {
);
}
- /**
- * Returns a copy of the targets which has qs shortcut turned on
- */
- public ArraySet<String> getA11yQsTargets() {
- return new ArraySet<>(mAccessibilityQsTargets);
- }
-
public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
mA11yTilesInQsPanel.clear();
mA11yTilesInQsPanel.addAll(componentNames);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 9a81aa6cc506..b7fd09f7b594 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -434,21 +434,23 @@ public class AccessibilityWindowManager {
}
/**
- * Callbacks from window manager when there's an accessibility change in windows.
+ * Called when the windows for accessibility changed.
*
- * @param forceSend Send the windows for accessibility even if they haven't changed.
- * @param topFocusedDisplayId The display Id which has the top focused window.
+ * @param forceSend Send the windows for accessibility even if they haven't
+ * changed.
+ * @param topFocusedDisplayId The display Id which has the top focused window.
* @param topFocusedWindowToken The window token of top focused window.
- * @param windows The windows for accessibility.
+ * @param screenSize The size of the display that the change happened.
+ * @param accessibilityWindows The windows for accessibility.
*/
@Override
- public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
- IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) {
+ public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId,
+ @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize,
+ @NonNull List<AccessibilityWindow> accessibilityWindows) {
synchronized (mLock) {
- if (!Flags.computeWindowChangesOnA11yV2()) {
- // If the flag is enabled, it's already done in #createWindowInfoListLocked.
- updateWindowsByWindowAttributesLocked(windows);
- }
+ final List<WindowInfo> windows =
+ createWindowInfoListLocked(screenSize, accessibilityWindows);
+
if (DEBUG) {
Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+ "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
@@ -463,14 +465,15 @@ public class AccessibilityWindowManager {
Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
}
}
- if (shouldUpdateWindowsLocked(forceSend, windows)) {
+
+ if (forceSend || shouldUpdateWindowsLocked(windows)) {
mTopFocusedDisplayId = topFocusedDisplayId;
if (!isProxyed(topFocusedDisplayId)) {
mLastNonProxyTopFocusedDisplayId = topFocusedDisplayId;
}
mTopFocusedWindowToken = topFocusedWindowToken;
if (DEBUG) {
- Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+ Slogf.d(LOG_TAG, "onAccessibilityWindowsChanged(): updating windows for "
+ "display %d and token %s",
topFocusedDisplayId, topFocusedWindowToken);
}
@@ -480,39 +483,14 @@ public class AccessibilityWindowManager {
windows);
// Someone may be waiting for the windows - advertise it.
mLock.notifyAll();
- }
- else if (DEBUG) {
- Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+ } else if (DEBUG) {
+ Slogf.d(LOG_TAG, "onAccessibilityWindowsChanged(): NOT updating windows for "
+ "display %d and token %s",
topFocusedDisplayId, topFocusedWindowToken);
}
}
}
- /**
- * Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
- * true.
- *
- * @param forceSend Send the windows for accessibility even if they haven't
- * changed.
- * @param topFocusedDisplayId The display Id which has the top focused window.
- * @param topFocusedWindowToken The window token of top focused window.
- * @param screenSize The size of the display that the change happened.
- * @param windows The windows for accessibility.
- */
- @Override
- public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId,
- @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize,
- @NonNull List<AccessibilityWindow> windows) {
- synchronized (mLock) {
- final List<WindowInfo> windowInfoList =
- createWindowInfoListLocked(screenSize, windows);
- onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
- topFocusedWindowToken, windowInfoList);
- }
- }
-
private List<WindowInfo> createWindowInfoListLocked(@NonNull Point screenSize,
@NonNull List<AccessibilityWindow> visibleWindows) {
final Set<IBinder> addedWindows = new ArraySet<>();
@@ -655,16 +633,6 @@ public class AccessibilityWindowManager {
return true;
}
- private void updateWindowsByWindowAttributesLocked(List<WindowInfo> windows) {
- for (int i = windows.size() - 1; i >= 0; i--) {
- final WindowInfo windowInfo = windows.get(i);
- final IBinder token = windowInfo.token;
- final int windowId = findWindowIdLocked(
- mAccessibilityUserManager.getCurrentUserIdLocked(), token);
- updateWindowWithWindowAttributes(windowInfo, mWindowAttributes.get(windowId));
- }
- }
-
private void updateWindowWithWindowAttributes(@NonNull WindowInfo windowInfo,
@Nullable AccessibilityWindowAttributes attributes) {
if (attributes == null) {
@@ -674,12 +642,7 @@ public class AccessibilityWindowManager {
windowInfo.locales = attributes.getLocales();
}
- private boolean shouldUpdateWindowsLocked(boolean forceSend,
- @NonNull List<WindowInfo> windows) {
- if (forceSend) {
- return true;
- }
-
+ private boolean shouldUpdateWindowsLocked(@NonNull List<WindowInfo> windows) {
final int windowCount = windows.size();
if (VERBOSE) {
Slogf.v(LOG_TAG,
@@ -869,20 +832,12 @@ public class AccessibilityWindowManager {
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
- boolean hasWindowIgnore = false;
if (windowCount > 0) {
- for (int i = 0; i < windowCount; i++) {
- final WindowInfo windowInfo = windows.get(i);
- final AccessibilityWindowInfo window;
- if (mTrackingWindows) {
- window = populateReportedWindowLocked(userId, windowInfo, oldWindowsById);
- if (window == null) {
- hasWindowIgnore = true;
- }
- } else {
- window = null;
- }
- if (window != null) {
+ if (mTrackingWindows) {
+ for (int i = 0; i < windowCount; i++) {
+ final WindowInfo windowInfo = windows.get(i);
+ final AccessibilityWindowInfo window =
+ populateReportedWindowLocked(userId, windowInfo, oldWindowsById);
// Flip layers in list to be consistent with AccessibilityService#getWindows
window.setLayer(windowCount - 1 - window.getLayer());
@@ -907,13 +862,6 @@ public class AccessibilityWindowManager {
}
}
final int accessibilityWindowCount = mWindows.size();
- // Re-order the window layer of all windows in the windows list because there's
- // window not been added into the windows list.
- if (hasWindowIgnore) {
- for (int i = 0; i < accessibilityWindowCount; i++) {
- mWindows.get(i).setLayer(accessibilityWindowCount - 1 - i);
- }
- }
if (isTopFocusedDisplay) {
if (mTouchInteractionInProgress && activeWindowGone) {
mActiveWindowId = mTopFocusedWindowId;
@@ -990,19 +938,6 @@ public class AccessibilityWindowManager {
private AccessibilityWindowInfo populateReportedWindowLocked(int userId,
WindowInfo window, SparseArray<AccessibilityWindowInfo> oldWindowsById) {
final int windowId = findWindowIdLocked(userId, window.token);
-
- // With the flag enabled, createWindowInfoListLocked() already removes invalid windows.
- if (!Flags.computeWindowChangesOnA11yV2()) {
- if (windowId < 0) {
- return null;
- }
-
- // Don't need to add the embedded hierarchy windows into the a11y windows list.
- if (isEmbeddedHierarchyWindowsLocked(windowId)) {
- return null;
- }
- }
-
final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
reportedWindow.setId(windowId);
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 54368ca9c03e..1212c757f1c2 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -73,12 +73,16 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
private static final int MESSAGE_MOVE_MOUSE_POINTER = 1;
private static final int MESSAGE_SCROLL_MOUSE_POINTER = 2;
- private static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f;
private static final int KEY_NOT_SET = -1;
/** Time interval after which mouse action will be repeated */
private static final int INTERVAL_MILLIS = 10;
+ @VisibleForTesting
+ public static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f;
+ @VisibleForTesting
+ public static final float MOUSE_SCROLL_STEP = 0.2f;
+
private final AccessibilityManagerService mAms;
private final Handler mHandler;
private final InputManager mInputManager;
@@ -134,8 +138,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9),
- LEFT_MOVE(KeyEvent.KEYCODE_U),
- RIGHT_MOVE(KeyEvent.KEYCODE_O),
+ LEFT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_U),
+ RIGHT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_O),
DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J),
DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K),
DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L),
@@ -263,6 +267,16 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
);
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void sendVirtualMouseScrollEvent(float x, float y) {
+ waitForVirtualMouseCreation();
+ mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(x)
+ .setYAxisMovement(y)
+ .build()
+ );
+ }
+
/**
* Performs a mouse scroll action based on the provided key code.
* The scroll action will only be performed if the scroll toggle is on.
@@ -280,19 +294,31 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
private void performMouseScrollAction(int keyCode) {
MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
- float y = switch (mouseKeyEvent) {
- case UP_MOVE_OR_SCROLL -> 1.0f;
- case DOWN_MOVE_OR_SCROLL -> -1.0f;
- default -> 0.0f;
- };
- waitForVirtualMouseCreation();
- mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
- .setYAxisMovement(y)
- .build()
- );
+ float x = 0f;
+ float y = 0f;
+
+ switch (mouseKeyEvent) {
+ case UP_MOVE_OR_SCROLL -> {
+ y = MOUSE_SCROLL_STEP;
+ }
+ case DOWN_MOVE_OR_SCROLL -> {
+ y = -MOUSE_SCROLL_STEP;
+ }
+ case LEFT_MOVE_OR_SCROLL -> {
+ x = MOUSE_SCROLL_STEP;
+ }
+ case RIGHT_MOVE_OR_SCROLL -> {
+ x = -MOUSE_SCROLL_STEP;
+ }
+ default -> {
+ x = 0.0f;
+ y = 0.0f;
+ }
+ }
+ sendVirtualMouseScrollEvent(x, y);
if (DEBUG) {
Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
- + " for scroll action with axis movement (y=" + y + ")");
+ + " for scroll action with axis movement (x=" + x + ", y=" + y + ")");
}
}
@@ -340,8 +366,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
* The method calculates the relative movement of the mouse pointer
* and sends the corresponding event to the virtual mouse.
*
- * The UP and DOWN pointer actions will only take place for their respective keys
- * if the scroll toggle is off.
+ * The UP, DOWN, LEFT, RIGHT pointer actions will only take place for their
+ * respective keys if the scroll toggle is off.
*
* @param keyCode The key code representing the direction or button press.
* Supported keys are:
@@ -349,8 +375,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE_OR_SCROLL}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE_OR_SCROLL}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE}
@@ -377,10 +403,10 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
}
- case LEFT_MOVE -> {
+ case LEFT_MOVE_OR_SCROLL -> {
x = -MOUSE_POINTER_MOVEMENT_STEP;
}
- case RIGHT_MOVE -> {
+ case RIGHT_MOVE_OR_SCROLL -> {
x = MOUSE_POINTER_MOVEMENT_STEP;
}
case DIAGONAL_UP_LEFT_MOVE -> {
@@ -420,7 +446,9 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
private boolean isMouseScrollKey(int keyCode, InputDevice inputDevice) {
return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCode(inputDevice)
- || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice);
+ || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice)
+ || keyCode == MouseKeyEvent.LEFT_MOVE_OR_SCROLL.getKeyCode(inputDevice)
+ || keyCode == MouseKeyEvent.RIGHT_MOVE_OR_SCROLL.getKeyCode(inputDevice);
}
/**
@@ -597,7 +625,9 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
});
mHandler.removeCallbacksAndMessages(null);
- mVirtualDevice.close();
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 4cb3d247edb0..cd97d838e3a0 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -109,14 +109,11 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
return mDeviceId;
}
- /**
- * Called when the proxy is registered.
- */
- void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
- throws RemoteException {
- mServiceInterface = serviceInterface;
- mService = serviceInterface.asBinder();
- mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+ /** Called when the proxy is registered. */
+ void initializeClient(IAccessibilityServiceClient client) throws RemoteException {
+ mClient = client;
+ mClientBinder = client.asBinder();
+ mClient.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index b4deeb0a6872..f8551457d04d 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -183,14 +183,12 @@ public class ProxyManager {
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
- if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
- if (mAppsOnVirtualDeviceListener == null) {
- mAppsOnVirtualDeviceListener = allRunningUids ->
- notifyProxyOfRunningAppsChange(allRunningUids);
- final VirtualDeviceManagerInternal localVdm = getLocalVdm();
- if (localVdm != null) {
- localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
- }
+ if (mAppsOnVirtualDeviceListener == null) {
+ mAppsOnVirtualDeviceListener = allRunningUids ->
+ notifyProxyOfRunningAppsChange(allRunningUids);
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm != null) {
+ localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
}
}
if (mProxyA11yServiceConnections.size() == 1) {
@@ -214,7 +212,7 @@ public class ProxyManager {
mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
}
});
- connection.initializeServiceInterface(client);
+ connection.initializeClient(client);
}
private void registerVirtualDeviceListener() {
@@ -331,14 +329,12 @@ public class ProxyManager {
// device.
if (!isProxyedDeviceId(deviceId)) {
synchronized (mLock) {
- if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
- if (mProxyA11yServiceConnections.size() == 0) {
- final VirtualDeviceManagerInternal localVdm = getLocalVdm();
- if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
- localVdm.unregisterAppsOnVirtualDeviceListener(
- mAppsOnVirtualDeviceListener);
- mAppsOnVirtualDeviceListener = null;
- }
+ if (mProxyA11yServiceConnections.size() == 0) {
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
+ localVdm.unregisterAppsOnVirtualDeviceListener(
+ mAppsOnVirtualDeviceListener);
+ mAppsOnVirtualDeviceListener = null;
}
}
mSystemSupport.removeDeviceIdLocked(deviceId);
@@ -561,8 +557,8 @@ public class ProxyManager {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
- final IBinder proxyBinder = proxy.mService;
- final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+ final IBinder proxyBinder = proxy.mClientBinder;
+ final IAccessibilityServiceClient proxyInterface = proxy.mClient;
if ((proxyBinder != null) && (proxyInterface != null)) {
interfaces.add(proxyInterface);
}
@@ -671,8 +667,7 @@ public class ProxyManager {
+ getLastSentStateLocked(deviceId));
Slog.v(LOG_TAG, "force update: " + forceUpdate);
}
- if ((getLastSentStateLocked(deviceId)) != proxyState
- || (Flags.proxyUseAppsOnVirtualDeviceListener() && forceUpdate)) {
+ if ((getLastSentStateLocked(deviceId)) != proxyState || forceUpdate) {
setLastStateLocked(deviceId, proxyState);
mMainHandler.post(() -> {
synchronized (mLock) {
@@ -873,33 +868,22 @@ public class ProxyManager {
for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
final AccessibilityManagerService.Client client =
((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
- if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
- if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
- continue;
- }
- boolean uidBelongsToDevice =
- localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
- if (client.mDeviceId != deviceId && uidBelongsToDevice) {
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
- + Arrays.toString(client.mPackageNames));
- }
- client.mDeviceId = deviceId;
- } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
- client.mDeviceId = DEVICE_ID_DEFAULT;
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to the default device from device id "
- + deviceId + " are " + Arrays.toString(client.mPackageNames));
- }
+ if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
+ continue;
+ }
+ boolean uidBelongsToDevice =
+ localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
+ if (client.mDeviceId != deviceId && uidBelongsToDevice) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ + Arrays.toString(client.mPackageNames));
}
- } else {
- if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
- && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
- + Arrays.toString(client.mPackageNames));
- }
- client.mDeviceId = deviceId;
+ client.mDeviceId = deviceId;
+ } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
+ client.mDeviceId = DEVICE_ID_DEFAULT;
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to the default device from device id "
+ + deviceId + " are " + Arrays.toString(client.mPackageNames));
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f85d786f89c5..ed4eeb534412 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -107,8 +107,7 @@ class UiAutomationManager {
Binder.getCallingUserHandle().getIdentifier());
if (mUiAutomationService != null) {
throw new IllegalStateException(
- "UiAutomationService " + mUiAutomationService.mServiceInterface
- + "already registered!");
+ "UiAutomationService " + mUiAutomationService.mClient + "already registered!");
}
try {
@@ -130,10 +129,9 @@ class UiAutomationManager {
mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
systemActionPerformer, awm);
mUiAutomationServiceOwner = owner;
- mUiAutomationService.mServiceInterface = serviceClient;
+ mUiAutomationService.mClient = serviceClient;
try {
- mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
- 0);
+ mUiAutomationService.mClient.asBinder().linkToDeath(mUiAutomationService, 0);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed registering death link: " + re);
destroyUiAutomationService();
@@ -149,10 +147,10 @@ class UiAutomationManager {
synchronized (mLock) {
if (useAccessibility()
&& ((mUiAutomationService == null)
- || (serviceClient == null)
- || (mUiAutomationService.mServiceInterface == null)
- || (serviceClient.asBinder()
- != mUiAutomationService.mServiceInterface.asBinder()))) {
+ || (serviceClient == null)
+ || (mUiAutomationService.mClient == null)
+ || (serviceClient.asBinder()
+ != mUiAutomationService.mClient.asBinder()))) {
throw new IllegalStateException("UiAutomationService " + serviceClient
+ " not registered!");
}
@@ -230,8 +228,7 @@ class UiAutomationManager {
private void destroyUiAutomationService() {
synchronized (mLock) {
if (mUiAutomationService != null) {
- mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(
- mUiAutomationService, 0);
+ mUiAutomationService.mClient.asBinder().unlinkToDeath(mUiAutomationService, 0);
mUiAutomationService.onRemoved();
mUiAutomationService.resetLocked();
mUiAutomationService = null;
@@ -271,40 +268,48 @@ class UiAutomationManager {
void connectServiceUnknownThread() {
// This needs to be done on the main thread
- mMainHandler.post(() -> {
- try {
- final IAccessibilityServiceClient serviceInterface;
- final UiAutomationService uiAutomationService;
- synchronized (mLock) {
- serviceInterface = mServiceInterface;
- uiAutomationService = mUiAutomationService;
- if (serviceInterface == null) {
- mService = null;
- } else {
- mService = mServiceInterface.asBinder();
- mService.linkToDeath(this, 0);
+ mMainHandler.post(
+ () -> {
+ try {
+ final IAccessibilityServiceClient client;
+ final UiAutomationService uiAutomationService;
+ synchronized (mLock) {
+ client = mClient;
+ uiAutomationService = mUiAutomationService;
+ if (client == null) {
+ mClientBinder = null;
+ } else {
+ mClientBinder = mClient.asBinder();
+ mClientBinder.linkToDeath(this, 0);
+ }
+ }
+ // If the client is null, the UiAutomation has been shut down on
+ // another thread.
+ if (client != null && uiAutomationService != null) {
+ uiAutomationService.addWindowTokensForAllDisplays();
+ if (mTrace.isA11yTracingEnabledForTypes(
+ AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTrace.logTrace(
+ "UiAutomationService.connectServiceUnknownThread",
+ AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
+ "serviceConnection="
+ + this
+ + ";connectionId="
+ + mId
+ + "windowToken="
+ + mOverlayWindowTokens.get(
+ Display.DEFAULT_DISPLAY));
+ }
+ client.init(
+ this,
+ mId,
+ mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ }
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error initializing connection", re);
+ destroyUiAutomationService();
}
- }
- // If the serviceInterface is null, the UiAutomation has been shut down on
- // another thread.
- if (serviceInterface != null && uiAutomationService != null) {
- uiAutomationService.addWindowTokensForAllDisplays();
- if (mTrace.isA11yTracingEnabledForTypes(
- AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
- mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
- AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
- "serviceConnection=" + this + ";connectionId=" + mId
- + "windowToken="
- + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
- }
- serviceInterface.init(this, mId,
- mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
- }
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error initializing connection", re);
- destroyUiAutomationService();
- }
- });
+ });
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 0ed239e442e7..0cbbf6da022b 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -240,10 +240,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
private void clear(MotionEvent event, int policyFlags) {
- if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
- // If a touch exploration gesture is in progress send events for its end.
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
- }
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
mDraggingPointerId = INVALID_POINTER_ID;
// Send exit to any pointers that we have delivered as part of delegating or dragging.
mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
@@ -562,10 +559,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
// clear any hover events that might have been queued and never sent.
mSendHoverEnterAndMoveDelayed.clear();
mSendHoverExitDelayed.cancel();
- // If a touch exploration gesture is in progress send events for its end.
- if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
- }
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
if (mState.isClear()) {
if (!mSendHoverEnterAndMoveDelayed.isPending()) {
// Queue a delayed transition to STATE_TOUCH_EXPLORING.
@@ -1599,9 +1593,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (mEvents.size() == 0) {
return;
}
- if (Flags.sendHoverEventsBasedOnEventStream()) {
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
- }
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
// Send an accessibility event to announce the touch exploration start.
mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 6b6b39df24d7..11b8ccb70dfb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -47,7 +47,6 @@ import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.View;
@@ -65,6 +64,7 @@ import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.util.ArrayList;
@@ -397,7 +397,7 @@ public class FullScreenMagnificationController implements
mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
sendSpecToAnimation(mCurrentMagnificationSpec, null);
}
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
magnified.recycle();
}
@@ -475,8 +475,16 @@ public class FullScreenMagnificationController implements
return mIdOfLastServiceToMagnify;
}
+ /**
+ * This is invoked whenever magnification change happens.
+ *
+ * @param isScaleTransient represents that if the scale is being changed and the changed
+ * value may be short lived and be updated again soon.
+ * Calling the method usually notifies input manager to update the
+ * cursor scale, but setting this value {@code true} prevents it.
+ */
@GuardedBy("mLock")
- void onMagnificationChangedLocked() {
+ void onMagnificationChangedLocked(boolean isScaleTransient) {
final float scale = getScale();
final float centerX = getCenterX();
final float centerY = getCenterY();
@@ -499,6 +507,10 @@ public class FullScreenMagnificationController implements
} else {
hideThumbnail();
}
+
+ if (!isScaleTransient) {
+ notifyScaleForInput(mDisplayId, scale);
+ }
}
@GuardedBy("mLock")
@@ -612,8 +624,9 @@ public class FullScreenMagnificationController implements
* Directly Zooms out the scale to 1f with animating the transition. This method is
* triggered only by service automatically, such as when user context changed.
*/
+ @GuardedBy("mLock")
void zoomOutFromService() {
- setScaleAndCenter(1.0f, Float.NaN, Float.NaN,
+ setScaleAndCenter(1.0f, Float.NaN, Float.NaN, /* isScaleTransient= */ false,
transformToStubCallback(true),
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mZoomedOutFromService = true;
@@ -641,7 +654,7 @@ public class FullScreenMagnificationController implements
setActivated(false);
if (changed) {
spec.clear();
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
sendSpecToAnimation(spec, animationCallback);
@@ -652,7 +665,7 @@ public class FullScreenMagnificationController implements
}
@GuardedBy("mLock")
- boolean setScale(float scale, float pivotX, float pivotY,
+ boolean setScale(float scale, float pivotX, float pivotY, boolean isScaleTransient,
boolean animate, int id) {
if (!mRegistered) {
return false;
@@ -675,19 +688,20 @@ public class FullScreenMagnificationController implements
final float centerX = normPivotX + offsetX;
final float centerY = normPivotY + offsetY;
mIdOfLastServiceToMagnify = id;
- return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id);
+ return setScaleAndCenter(scale, centerX, centerY, isScaleTransient,
+ transformToStubCallback(animate), id);
}
@GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
- MagnificationAnimationCallback animationCallback, int id) {
+ boolean isScaleTransient, MagnificationAnimationCallback animationCallback,
+ int id) {
if (!mRegistered) {
return false;
}
- // If the border implementation is on system ui side but the connection is not
+ // The border implementation is on system ui side but the connection is not
// established, the fullscreen magnification should not work.
- if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
- && !mMagnificationConnectionStateSupplier.get()) {
+ if (!mMagnificationConnectionStateSupplier.get()) {
return false;
}
if (DEBUG) {
@@ -697,7 +711,7 @@ public class FullScreenMagnificationController implements
+ animationCallback + ", id = " + id + ")");
}
boolean changed = setActivated(true);
- changed |= updateMagnificationSpecLocked(scale, centerX, centerY);
+ changed |= updateMagnificationSpecLocked(scale, centerX, centerY, isScaleTransient);
sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
if (isActivated() && (id != INVALID_SERVICE_ID)) {
mIdOfLastServiceToMagnify = id;
@@ -774,7 +788,9 @@ public class FullScreenMagnificationController implements
* @return {@code true} if the magnification spec changed or {@code false}
* otherwise
*/
- boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+ @GuardedBy("mLock")
+ boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY,
+ boolean isScaleTransient) {
// Handle defaults.
if (Float.isNaN(centerX)) {
centerX = getCenterX();
@@ -802,7 +818,7 @@ public class FullScreenMagnificationController implements
changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
if (changed) {
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(isScaleTransient);
}
return changed;
@@ -817,7 +833,7 @@ public class FullScreenMagnificationController implements
final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
if (id != INVALID_SERVICE_ID) {
mIdOfLastServiceToMagnify = id;
@@ -862,7 +878,7 @@ public class FullScreenMagnificationController implements
}
synchronized (mLock) {
mCurrentMagnificationSpec.setTo(lastSpecSent);
- onMagnificationChangedLocked();
+ onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
}
});
@@ -956,6 +972,7 @@ public class FullScreenMagnificationController implements
context,
traceManager,
LocalServices.getService(WindowManagerInternal.class),
+ LocalServices.getService(InputManagerInternal.class),
new Handler(context.getMainLooper()),
context.getResources().getInteger(R.integer.config_longAnimTime)),
lock,
@@ -1465,20 +1482,24 @@ public class FullScreenMagnificationController implements
* @param scale the target scale, must be >= 1
* @param pivotX the screen-relative X coordinate around which to scale
* @param pivotY the screen-relative Y coordinate around which to scale
+ * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+ * soon. {@code false} otherwise.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
- boolean animate, int id) {
+ boolean isScaleTransient, boolean animate, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
- return display.setScale(scale, pivotX, pivotY, animate, id);
+ return display.setScale(scale, pivotX, pivotY, isScaleTransient, animate, id);
}
}
@@ -1497,6 +1518,8 @@ public class FullScreenMagnificationController implements
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
@@ -1504,7 +1527,7 @@ public class FullScreenMagnificationController implements
return false;
}
return display.setScaleAndCenter(Float.NaN, centerX, centerY,
- animate ? STUB_ANIMATION_CALLBACK : null, id);
+ /* isScaleTransient= */ false, animate ? STUB_ANIMATION_CALLBACK : null, id);
}
}
@@ -1527,7 +1550,32 @@ public class FullScreenMagnificationController implements
*/
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate, int id) {
- return setScaleAndCenter(displayId, scale, centerX, centerY,
+ return setScaleAndCenter(displayId, scale, centerX, centerY, /* isScaleTransient= */ false,
+ transformToStubCallback(animate), id);
+ }
+
+ /**
+ * Sets the scale and center of the magnified region, optionally
+ * animating the transition. If animation is disabled, the transition
+ * is immediate.
+ *
+ * @param displayId The logical display id.
+ * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+ * @param centerX the screen-relative X coordinate around which to
+ * center and scale, or {@link Float#NaN} to leave unchanged
+ * @param centerY the screen-relative Y coordinate around which to
+ * center and scale, or {@link Float#NaN} to leave unchanged
+ * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+ * soon. {@code false} otherwise.
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ * @param id the ID of the service requesting the change
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
+ */
+ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+ boolean isScaleTransient, boolean animate, int id) {
+ return setScaleAndCenter(displayId, scale, centerX, centerY, isScaleTransient,
transformToStubCallback(animate), id);
}
@@ -1542,20 +1590,25 @@ public class FullScreenMagnificationController implements
* center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
+ * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed
+ * soon. {@code false} otherwise.
* @param animationCallback Called when the animation result is valid.
* {@code null} to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
- MagnificationAnimationCallback animationCallback, int id) {
+ boolean isScaleTransient, MagnificationAnimationCallback animationCallback, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
- return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id);
+ return display.setScaleAndCenter(scale, centerX, centerY, isScaleTransient,
+ animationCallback, id);
}
}
@@ -1570,6 +1623,8 @@ public class FullScreenMagnificationController implements
* screen pixels.
* @param id the ID of the service requesting the change
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
@@ -1637,9 +1692,12 @@ public class FullScreenMagnificationController implements
* <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
* We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
* will be no obvious magnification effect.
+ * Only the value of the default display is persisted in user's settings.
*/
public void persistScale(int displayId) {
- final float scale = getScale(Display.DEFAULT_DISPLAY);
+ final float scale = getScale(displayId);
+ notifyScaleForInput(displayId, scale);
+
if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
@@ -1665,6 +1723,8 @@ public class FullScreenMagnificationController implements
*
* @param displayId The logical display id.
*/
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
private void zoomOutFromService(int displayId) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
@@ -1691,6 +1751,20 @@ public class FullScreenMagnificationController implements
}
/**
+ * Notifies input manager that magnification scale changed non-transiently
+ * so that pointer cursor is scaled as well.
+ *
+ * @param displayId The logical display id.
+ * @param scale The new scale factor.
+ */
+ public void notifyScaleForInput(int displayId, float scale) {
+ if (Flags.magnificationEnlargePointerBugfix()) {
+ mControllerCtx.getInputManager()
+ .setAccessibilityPointerIconScaleFactor(displayId, scale);
+ }
+ }
+
+ /**
* Resets all displays' magnification if last magnifying service is disabled.
*
* @param connectionId
@@ -2166,6 +2240,7 @@ public class FullScreenMagnificationController implements
private final Context mContext;
private final AccessibilityTraceManager mTrace;
private final WindowManagerInternal mWindowManager;
+ private final InputManagerInternal mInputManager;
private final Handler mHandler;
private final Long mAnimationDuration;
@@ -2175,11 +2250,13 @@ public class FullScreenMagnificationController implements
public ControllerContext(@NonNull Context context,
@NonNull AccessibilityTraceManager traceManager,
@NonNull WindowManagerInternal windowManager,
+ @NonNull InputManagerInternal inputManager,
@NonNull Handler handler,
long animationDuration) {
mContext = context;
mTrace = traceManager;
mWindowManager = windowManager;
+ mInputManager = inputManager;
mHandler = handler;
mAnimationDuration = animationDuration;
}
@@ -2209,6 +2286,14 @@ public class FullScreenMagnificationController implements
}
/**
+ * @return InputManagerInternal
+ */
+ @NonNull
+ public InputManagerInternal getInputManager() {
+ return mInputManager;
+ }
+
+ /**
* @return Handler for main looper
*/
@NonNull
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index a19fdddea49c..d11ae0a6ad97 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -58,8 +58,6 @@ import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.VelocityTracker;
@@ -155,9 +153,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@VisibleForTesting State mCurrentState;
@VisibleForTesting State mPreviousState;
- private PointerCoords[] mTempPointerCoords;
- private PointerProperties[] mTempPointerProperties;
-
@VisibleForTesting static final int OVERSCROLL_NONE = 0;
@VisibleForTesting static final int OVERSCROLL_LEFT_EDGE = 1;
@VisibleForTesting static final int OVERSCROLL_RIGHT_EDGE = 2;
@@ -345,7 +340,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@Override
void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (Flags.enableMagnificationFollowsMouse()) {
+ if (Flags.enableMagnificationFollowsMouseBugfix()) {
if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
// TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
// over, rather than only interacting with the current display.
@@ -430,38 +425,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
mPanningScalingState.clear();
}
- private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
- final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
- if (oldSize < size) {
- PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
- mTempPointerCoords = new PointerCoords[size];
- if (oldTempPointerCoords != null) {
- System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
- }
- }
- for (int i = oldSize; i < size; i++) {
- mTempPointerCoords[i] = new PointerCoords();
- }
- return mTempPointerCoords;
- }
-
- private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
- final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
- : 0;
- if (oldSize < size) {
- PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
- mTempPointerProperties = new PointerProperties[size];
- if (oldTempPointerProperties != null) {
- System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
- oldSize);
- }
- }
- for (int i = oldSize; i < size; i++) {
- mTempPointerProperties[i] = new PointerProperties();
- }
- return mTempPointerProperties;
- }
-
@VisibleForTesting
void transitionTo(State state) {
if (DEBUG_STATE_TRANSITIONS) {
@@ -617,7 +580,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
}
if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
- mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+ mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY,
+ /* isScaleTransient= */ true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
checkShouldDetectPassPersistedScale();
@@ -1206,7 +1170,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
- if (Flags.enableMagnificationFollowsMouse()
+ if (Flags.enableMagnificationFollowsMouseBugfix()
&& !event.isFromSource(SOURCE_TOUCHSCREEN)) {
// Only touch events need to be cached and sent later.
return;
@@ -1974,6 +1938,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
/* scale= */ scale,
/* centerX= */ mPivotEdge.x,
/* centerY= */ mPivotEdge.y,
+ /* isScaleTransient= */ true,
/* animate= */ true,
/* id= */ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
if (scale == 1.0f) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 1489d16c3764..058b2be5f4b3 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import android.accessibilityservice.MagnificationConfig;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -50,7 +51,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.wm.WindowManagerInternal;
-import com.android.window.flags.Flags;
import java.util.concurrent.Executor;
@@ -101,6 +101,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
/** Whether the platform supports window magnification feature. */
private final boolean mSupportWindowMagnification;
+ private final MagnificationScaleStepProvider mScaleStepProvider;
private final Executor mBackgroundExecutor;
@@ -131,6 +132,14 @@ public class MagnificationController implements MagnificationConnectionManager.C
.UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
new SparseArray<>();
+ // Direction magnifier scale can be altered.
+ public static final int ZOOM_DIRECTION_IN = 0;
+ public static final int ZOOM_DIRECTION_OUT = 1;
+
+ @IntDef({ZOOM_DIRECTION_IN, ZOOM_DIRECTION_OUT})
+ public @interface ZoomDirection {
+ }
+
/**
* A callback to inform the magnification transition result on the given display.
*/
@@ -144,6 +153,41 @@ public class MagnificationController implements MagnificationConnectionManager.C
void onResult(int displayId, boolean success);
}
+
+ /**
+ * An interface to configure how much the magnification scale should be affected when moving in
+ * steps.
+ */
+ public interface MagnificationScaleStepProvider {
+ /**
+ * Calculate the next value given which direction (in/out) to adjust the magnification
+ * scale.
+ *
+ * @param currentScale The current magnification scale value.
+ * @param direction Whether to zoom in or out.
+ * @return The next scale value.
+ */
+ float nextScaleStep(float currentScale, @ZoomDirection int direction);
+ }
+
+ public static class DefaultMagnificationScaleStepProvider implements
+ MagnificationScaleStepProvider {
+ // Factor of magnification scale. For example, when this value is 1.189, scale
+ // value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ...
+ // Note: this value is 2.0 ^ (1 / 4).
+ public static final float ZOOM_STEP_SCALE_FACTOR = 1.18920712f;
+
+ @Override
+ public float nextScaleStep(float currentScale, @ZoomDirection int direction) {
+ final int stepDelta = direction == ZOOM_DIRECTION_IN ? 1 : -1;
+ final long scaleIndex = Math.round(
+ Math.log(currentScale) / Math.log(ZOOM_STEP_SCALE_FACTOR));
+ final float nextScale = (float) Math.pow(ZOOM_STEP_SCALE_FACTOR,
+ scaleIndex + stepDelta);
+ return MagnificationScaleProvider.constrainScale(nextScale);
+ }
+ }
+
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, MagnificationScaleProvider scaleProvider,
Executor backgroundExecutor) {
@@ -156,6 +200,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
+ mScaleStepProvider = new DefaultMagnificationScaleStepProvider();
mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
@@ -176,7 +221,8 @@ public class MagnificationController implements MagnificationConnectionManager.C
public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
if (getFullScreenMagnificationController().isActivated(displayId)) {
getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
- Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
+ Float.NaN, Float.NaN, /* isScaleTransient= */ !updatePersistence, false,
+ MAGNIFICATION_GESTURE_HANDLER_ID);
if (updatePersistence) {
getFullScreenMagnificationController().persistScale(displayId);
}
@@ -371,7 +417,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
}
screenMagnificationController.setScaleAndCenter(displayId, targetScale,
magnificationCenter.x, magnificationCenter.y,
- magnificationAnimationCallback, id);
+ /* isScaleTransient= */ false, magnificationAnimationCallback, id);
} else {
if (screenMagnificationController.isRegistered(displayId)) {
screenMagnificationController.reset(displayId, false);
@@ -587,10 +633,8 @@ public class MagnificationController implements MagnificationConnectionManager.C
@Override
public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
- if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
- getMagnificationConnectionManager()
- .onFullscreenMagnificationActivationChanged(displayId, activated);
- }
+ getMagnificationConnectionManager()
+ .onFullscreenMagnificationActivationChanged(displayId, activated);
if (activated) {
synchronized (mLock) {
@@ -890,6 +934,37 @@ public class MagnificationController implements MagnificationConnectionManager.C
return isActivated;
}
+ /**
+ * Scales the magnifier on the given display one step in/out based on the zoomIn param.
+ *
+ * @param displayId The logical display id.
+ * @param direction Whether the scale should be zoomed in or out.
+ * @return {@code true} if the magnification scale was affected.
+ */
+ public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) {
+ if (getFullScreenMagnificationController().isActivated(displayId)) {
+ final float magnificationScale = getFullScreenMagnificationController().getScale(
+ displayId);
+ final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+ magnificationScale, direction);
+ getFullScreenMagnificationController().setScaleAndCenter(displayId,
+ nextMagnificationScale,
+ Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID);
+ return nextMagnificationScale != magnificationScale;
+ }
+
+ if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) {
+ final float magnificationScale = getMagnificationConnectionManager().getScale(
+ displayId);
+ final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+ magnificationScale, direction);
+ getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale);
+ return nextMagnificationScale != magnificationScale;
+ }
+
+ return false;
+ }
+
private final class DisableMagnificationCallback implements
MagnificationAnimationCallback {
private final TransitionCallBack mTransitionCallBack;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 446123f07f64..fa86ba39bb1a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -146,7 +146,8 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo
} break;
case SOURCE_MOUSE:
case SOURCE_STYLUS: {
- if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) {
+ if (magnificationShortcutExists()
+ && Flags.enableMagnificationFollowsMouseBugfix()) {
handleMouseOrStylusEvent(event, rawEvent, policyFlags);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
index 6b48d2bacf9d..a4568aaa7a0d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
@@ -31,7 +31,6 @@ import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
import com.android.internal.R;
-import com.android.server.accessibility.Flags;
/**
* Handles the behavior while receiving scaling and panning gestures if it's enabled.
@@ -73,13 +72,9 @@ class PanningScalingHandler extends
mMaxScale = maxScale;
mMinScale = minScale;
mBlockScroll = blockScroll;
- if (Flags.pinchZoomZeroMinSpan()) {
- mScaleGestureDetector = new ScaleGestureDetector(context,
- ViewConfiguration.get(context).getScaledTouchSlop() * 2,
- /* minSpan= */ 0, Handler.getMain(), this);
- } else {
- mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
- }
+ mScaleGestureDetector = new ScaleGestureDetector(context,
+ ViewConfiguration.get(context).getScaledTouchSlop() * 2,
+ /* minSpan= */ 0, Handler.getMain(), this);
mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
mScaleGestureDetector.setQuickScaleEnabled(false);
mMagnificationDelegate = magnificationDelegate;
diff --git a/services/appfunctions/Android.bp b/services/appfunctions/Android.bp
index f8ee823ef0c9..7337aa26c145 100644
--- a/services/appfunctions/Android.bp
+++ b/services/appfunctions/Android.bp
@@ -19,7 +19,18 @@ java_library_static {
defaults: ["platform_service_defaults"],
srcs: [
":services.appfunctions-sources",
+ ":statslog-appfunctions-java-gen",
"java/**/*.logtags",
],
libs: ["services.core"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
+}
+
+genrule {
+ name: "statslog-appfunctions-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module appfunctions --javaPackage com.android.server.appfunctions --javaClass AppFunctionsStatsLog --minApiLevel 35",
+ out: ["java/com/android/server/appfunctions/AppFunctionsStatsLog.java"],
}
diff --git a/services/appfunctions/TEST_MAPPING b/services/appfunctions/TEST_MAPPING
index 91cfa064d9fc..851d754af943 100644
--- a/services/appfunctions/TEST_MAPPING
+++ b/services/appfunctions/TEST_MAPPING
@@ -2,11 +2,6 @@
"presubmit": [
{
"name": "FrameworksAppFunctionsTests"
- }
- ],
- "postsubmit": [
- {
- "name": "FrameworksAppFunctionsTests"
},
{
"name": "CtsAppFunctionTestCases"
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
new file mode 100644
index 000000000000..a832545475bf
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.RequiresPermission;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.util.IndentingPrintWriter;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
+import android.app.appfunctions.AppFunctionStaticMetadataHelper;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
+import android.app.appsearch.JoinSpec;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchSpec;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+public final class AppFunctionDumpHelper {
+ private static final String TAG = AppFunctionDumpHelper.class.getSimpleName();
+
+ private AppFunctionDumpHelper() {}
+
+ /** Dumps the state of all app functions for all users. */
+ @BinderThread
+ @RequiresPermission(
+ anyOf = {Manifest.permission.CREATE_USERS, Manifest.permission.MANAGE_USERS})
+ public static void dumpAppFunctionsState(@NonNull Context context, @NonNull PrintWriter w) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ if (userManager == null) {
+ w.println("Couldn't retrieve UserManager.");
+ return;
+ }
+
+ IndentingPrintWriter pw = new IndentingPrintWriter(w);
+
+ List<UserInfo> userInfos = userManager.getAliveUsers();
+ for (UserInfo userInfo : userInfos) {
+ pw.println(
+ "AppFunction state for user " + userInfo.getUserHandle().getIdentifier() + ":");
+ pw.increaseIndent();
+ dumpAppFunctionsStateForUser(
+ context.createContextAsUser(userInfo.getUserHandle(), /* flags= */ 0), pw);
+ pw.decreaseIndent();
+ }
+ }
+
+ private static void dumpAppFunctionsStateForUser(
+ @NonNull Context context, @NonNull IndentingPrintWriter pw) {
+ AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
+ if (appSearchManager == null) {
+ pw.println("Couldn't retrieve AppSearchManager.");
+ return;
+ }
+
+ try (FutureGlobalSearchSession searchSession =
+ new FutureGlobalSearchSession(appSearchManager, Runnable::run)) {
+ pw.println();
+
+ try (FutureSearchResults futureSearchResults =
+ searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); ) {
+ List<SearchResult> searchResultsList;
+ do {
+ searchResultsList = futureSearchResults.getNextPage().get();
+ for (SearchResult searchResult : searchResultsList) {
+ dumpAppFunctionMetadata(pw, searchResult);
+ }
+ } while (!searchResultsList.isEmpty());
+ }
+
+ } catch (Exception e) {
+ pw.println("Failed to dump AppFunction state: " + e);
+ }
+ }
+
+ private static SearchSpec buildAppFunctionMetadataSearchSpec() {
+ SearchSpec runtimeMetadataSearchSpec =
+ new SearchSpec.Builder()
+ .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+ .addFilterSchemas(AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE)
+ .build();
+ JoinSpec joinSpec =
+ new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+ .setNestedSearch(/* queryExpression= */ "", runtimeMetadataSearchSpec)
+ .build();
+
+ return new SearchSpec.Builder()
+ .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+ .addFilterSchemas(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)
+ .setJoinSpec(joinSpec)
+ .build();
+ }
+
+ private static void dumpAppFunctionMetadata(
+ IndentingPrintWriter pw, SearchResult joinedSearchResult) {
+ pw.println(
+ "AppFunctionMetadata for: "
+ + joinedSearchResult
+ .getGenericDocument()
+ .getPropertyString(PROPERTY_FUNCTION_ID));
+ pw.increaseIndent();
+
+ pw.println("Static Metadata:");
+ pw.increaseIndent();
+ writeGenericDocumentProperties(pw, joinedSearchResult.getGenericDocument());
+ pw.decreaseIndent();
+
+ pw.println("Runtime Metadata:");
+ pw.increaseIndent();
+ if (!joinedSearchResult.getJoinedResults().isEmpty()) {
+ writeGenericDocumentProperties(
+ pw, joinedSearchResult.getJoinedResults().getFirst().getGenericDocument());
+ } else {
+ pw.println("No runtime metadata found.");
+ }
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+
+ private static void writeGenericDocumentProperties(
+ IndentingPrintWriter pw, GenericDocument genericDocument) {
+ Set<String> propertyNames = genericDocument.getPropertyNames();
+ pw.println("{");
+ pw.increaseIndent();
+ for (String propertyName : propertyNames) {
+ Object propertyValue = genericDocument.getProperty(propertyName);
+ pw.print("\"" + propertyName + "\"" + ": [");
+
+ if (propertyValue instanceof GenericDocument[]) {
+ GenericDocument[] documentValues = (GenericDocument[]) propertyValue;
+ for (int i = 0; i < documentValues.length; i++) {
+ GenericDocument documentValue = documentValues[i];
+ writeGenericDocumentProperties(pw, documentValue);
+ if (i != documentValues.length - 1) {
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ } else {
+ int propertyArrLength = Array.getLength(propertyValue);
+ for (int i = 0; i < propertyArrLength; i++) {
+ Object propertyElement = Array.get(propertyValue, i);
+ if (propertyElement instanceof String) {
+ pw.print("\"" + propertyElement + "\"");
+ } else if (propertyElement instanceof byte[]) {
+ pw.print(Arrays.toString((byte[]) propertyElement));
+ } else if (propertyElement != null) {
+ pw.print(propertyElement.toString());
+ }
+ if (i != propertyArrLength - 1) {
+ pw.print(", ");
+ }
+ }
+ }
+ pw.println("]");
+ }
+ pw.decreaseIndent();
+ pw.println("}");
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index c3b7087a44c3..eaea4435099c 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,7 +16,8 @@
package com.android.server.appfunctions;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -24,14 +25,26 @@ import java.util.concurrent.TimeUnit;
/** Executors for App function operations. */
public final class AppFunctionExecutors {
+ static final int sConcurrency = Runtime.getRuntime().availableProcessors();
+
/** Executor for operations that do not need to block. */
- public static final Executor THREAD_POOL_EXECUTOR =
+ public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
- /* corePoolSize= */ Runtime.getRuntime().availableProcessors(),
- /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(),
- /* keepAliveTime= */ 0L,
+ /* corePoolSize= */ sConcurrency,
+ /* maxConcurrency= */ sConcurrency,
+ /* keepAliveTime= */ 1L,
/* unit= */ TimeUnit.SECONDS,
- /* workQueue= */ new LinkedBlockingQueue<>());
+ /* workQueue= */ new LinkedBlockingQueue<>(),
+ new NamedThreadFactory("AppFunctionExecutors"));
+
+ /** Executor for stats logging. */
+ public static final ExecutorService LOGGING_THREAD_EXECUTOR =
+ Executors.newSingleThreadExecutor(
+ new NamedThreadFactory("AppFunctionsLoggingExecutors"));
+
+ static {
+ THREAD_POOL_EXECUTOR.allowCoreThreadTimeOut(true);
+ }
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 1e723b5a1da2..9cc5a8c97258 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,20 +16,34 @@
package com.android.server.appfunctions;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
+
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.appfunctions.AppFunctionException;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.AppFunctionManagerHelper;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByDocumentIdRequest;
+import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.observer.DocumentChangeInfo;
import android.app.appsearch.observer.ObserverCallback;
import android.app.appsearch.observer.ObserverSpec;
@@ -37,17 +51,30 @@ import android.app.appsearch.observer.SchemaChangeInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemService.TargetUser;
-import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
-import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
import java.util.Objects;
+import java.util.WeakHashMap;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -58,6 +85,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
private final ServiceHelper mInternalServiceHelper;
private final ServiceConfig mServiceConfig;
private final Context mContext;
+ private final Map<String, Object> mLocks = new WeakHashMap<>();
+ private final AppFunctionsLoggerWrapper mLoggerWrapper;
public AppFunctionManagerServiceImpl(@NonNull Context context) {
this(
@@ -66,7 +95,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
new CallerValidatorImpl(context),
new ServiceHelperImpl(context),
- new ServiceConfigImpl());
+ new ServiceConfigImpl(),
+ new AppFunctionsLoggerWrapper(context));
}
@VisibleForTesting
@@ -75,12 +105,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
RemoteServiceCaller<IAppFunctionService> remoteServiceCaller,
CallerValidator callerValidator,
ServiceHelper appFunctionInternalServiceHelper,
- ServiceConfig serviceConfig) {
+ ServiceConfig serviceConfig,
+ AppFunctionsLoggerWrapper loggerWrapper) {
mContext = Objects.requireNonNull(context);
mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
mCallerValidator = Objects.requireNonNull(callerValidator);
mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper);
mServiceConfig = serviceConfig;
+ mLoggerWrapper = loggerWrapper;
}
/** Called when the user is unlocked. */
@@ -99,14 +131,45 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
@Override
- public void executeAppFunction(
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ AppFunctionDumpHelper.dumpAppFunctionsState(mContext, pw);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public ICancellationSignal executeAppFunction(
@NonNull ExecuteAppFunctionAidlRequest requestInternal,
@NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
Objects.requireNonNull(requestInternal);
Objects.requireNonNull(executeAppFunctionCallback);
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+
final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
- new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback);
+ new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback,
+ new SafeOneTimeExecuteAppFunctionCallback.CompletionCallback() {
+ @Override
+ public void finalizeOnSuccess(
+ @NonNull ExecuteAppFunctionResponse result) {
+ mLoggerWrapper.logAppFunctionSuccess(requestInternal, result,
+ callingUid);
+ }
+
+ @Override
+ public void finalizeOnError(@NonNull AppFunctionException error) {
+ mLoggerWrapper.logAppFunctionError(requestInternal,
+ error.getErrorCode(), callingUid);
+ }
+ });
String validatedCallingPackage;
try {
@@ -115,16 +178,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
mCallerValidator.verifyTargetUserHandle(
requestInternal.getUserHandle(), validatedCallingPackage);
} catch (SecurityException exception) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- exception.getMessage(),
- /* extras= */ null));
- return;
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_DENIED, exception.getMessage()));
+ return null;
}
- int callingUid = Binder.getCallingUid();
- int callingPid = Binder.getCallingUid();
+ ICancellationSignal localCancelTransport = CancellationSignal.createTransport();
+
THREAD_POOL_EXECUTOR.execute(
() -> {
try {
@@ -132,168 +193,299 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
requestInternal,
callingUid,
callingPid,
- safeExecuteAppFunctionCallback);
+ localCancelTransport,
+ safeExecuteAppFunctionCallback,
+ executeAppFunctionCallback.asBinder());
} catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
+ safeExecuteAppFunctionCallback.onError(
mapExceptionToExecuteAppFunctionResponse(e));
}
});
+ return localCancelTransport;
}
@WorkerThread
private void executeAppFunctionInternal(
- ExecuteAppFunctionAidlRequest requestInternal,
+ @NonNull ExecuteAppFunctionAidlRequest requestInternal,
int callingUid,
int callingPid,
- SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ @NonNull ICancellationSignal localCancelTransport,
+ @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
+ @NonNull IBinder callerBinder) {
UserHandle targetUser = requestInternal.getUserHandle();
- // TODO(b/354956319): Add and honor the new enterprise policies.
- if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- "Cannot run on a device with a device owner or from the managed"
- + " profile.",
- /* extras= */ null));
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+ if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) {
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED,
+ "Cannot run on a user with a restricted enterprise policy"));
return;
}
String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
if (TextUtils.isEmpty(targetPackageName)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
- "Target package name cannot be empty.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_INVALID_ARGUMENT,
+ "Target package name cannot be empty."));
return;
}
- var unused =
- mCallerValidator
- .verifyCallerCanExecuteAppFunction(
- callingUid,
- callingPid,
- requestInternal.getCallingPackage(),
- targetPackageName,
- requestInternal.getClientRequest().getFunctionIdentifier())
- .thenAccept(
- canExecute -> {
- if (!canExecute) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute"
- + " the appfunction",
- /* extras= */ null));
- return;
- }
- Intent serviceIntent =
- mInternalServiceHelper.resolveAppFunctionService(
- targetPackageName, targetUser);
- if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_INTERNAL_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
- return;
- }
- bindAppFunctionServiceUnchecked(
- requestInternal,
- serviceIntent,
- targetUser,
- safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE);
- })
- .exceptionally(
- ex -> {
- safeExecuteAppFunctionCallback.onResult(
- mapExceptionToExecuteAppFunctionResponse(ex));
- return null;
- });
+ mCallerValidator
+ .verifyCallerCanExecuteAppFunction(
+ callingUid,
+ callingPid,
+ targetUser,
+ requestInternal.getCallingPackage(),
+ targetPackageName,
+ requestInternal.getClientRequest().getFunctionIdentifier())
+ .thenAccept(
+ canExecute -> {
+ if (!canExecute) {
+ throw new SecurityException(
+ "Caller does not have permission to execute the"
+ + " appfunction");
+ }
+ })
+ .thenCompose(
+ isEnabled ->
+ isAppFunctionEnabled(
+ requestInternal.getClientRequest().getFunctionIdentifier(),
+ requestInternal.getClientRequest().getTargetPackageName(),
+ getAppSearchManagerAsUser(requestInternal.getUserHandle()),
+ THREAD_POOL_EXECUTOR))
+ .thenAccept(
+ isEnabled -> {
+ if (!isEnabled) {
+ throw new DisabledAppFunctionException(
+ "The app function is disabled");
+ }
+ })
+ .thenAccept(
+ unused -> {
+ Intent serviceIntent =
+ mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName, targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "Cannot find the target service."));
+ return;
+ }
+ bindAppFunctionServiceUnchecked(
+ requestInternal,
+ serviceIntent,
+ targetUser,
+ localCancelTransport,
+ safeExecuteAppFunctionCallback,
+ /* bindFlags= */ Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE,
+ callerBinder);
+ })
+ .exceptionally(
+ ex -> {
+ safeExecuteAppFunctionCallback.onError(
+ mapExceptionToExecuteAppFunctionResponse(ex));
+ return null;
+ });
+ }
+
+ private static AndroidFuture<Boolean> isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull AppSearchManager appSearchManager,
+ @NonNull Executor executor) {
+ AndroidFuture<Boolean> future = new AndroidFuture<>();
+ AppFunctionManagerHelper.isAppFunctionEnabled(
+ functionIdentifier,
+ targetPackage,
+ appSearchManager,
+ executor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull Boolean result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ future.completeExceptionally(error);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public void setAppFunctionEnabled(
+ @NonNull String callingPackage,
+ @NonNull String functionIdentifier,
+ @NonNull UserHandle userHandle,
+ @AppFunctionManager.EnabledState int enabledState,
+ @NonNull IAppFunctionEnabledCallback callback) {
+ try {
+ mCallerValidator.validateCallingPackage(callingPackage);
+ } catch (SecurityException e) {
+ reportException(callback, e);
+ return;
+ }
+ THREAD_POOL_EXECUTOR.execute(
+ () -> {
+ try {
+ synchronized (getLockForPackage(callingPackage)) {
+ setAppFunctionEnabledInternalLocked(
+ callingPackage, functionIdentifier, userHandle, enabledState);
+ }
+ callback.onSuccess();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in setAppFunctionEnabled: ", e);
+ reportException(callback, e);
+ }
+ });
+ }
+
+ private static void reportException(
+ @NonNull IAppFunctionEnabledCallback callback, @NonNull Exception exception) {
+ try {
+ callback.onError(new ParcelableException(exception));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to report the exception", e);
+ }
+ }
+
+ /**
+ * Sets the enabled status of a specified app function.
+ *
+ * <p>Required to hold a lock to call this function to avoid document changes during the
+ * process.
+ */
+ @WorkerThread
+ @GuardedBy("getLockForPackage(callingPackage)")
+ private void setAppFunctionEnabledInternalLocked(
+ @NonNull String callingPackage,
+ @NonNull String functionIdentifier,
+ @NonNull UserHandle userHandle,
+ @AppFunctionManager.EnabledState int enabledState)
+ throws Exception {
+ AppSearchManager perUserAppSearchManager = getAppSearchManagerAsUser(userHandle);
+
+ if (perUserAppSearchManager == null) {
+ throw new IllegalStateException(
+ "AppSearchManager not found for user:" + userHandle.getIdentifier());
+ }
+ SearchContext runtimeMetadataSearchContext =
+ new SearchContext.Builder(APP_FUNCTION_RUNTIME_METADATA_DB).build();
+
+ try (FutureAppSearchSession runtimeMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ perUserAppSearchManager,
+ THREAD_POOL_EXECUTOR,
+ runtimeMetadataSearchContext)) {
+ AppFunctionRuntimeMetadata existingMetadata =
+ new AppFunctionRuntimeMetadata(
+ getRuntimeMetadataGenericDocument(
+ callingPackage,
+ functionIdentifier,
+ runtimeMetadataSearchSession));
+ AppFunctionRuntimeMetadata newMetadata =
+ new AppFunctionRuntimeMetadata.Builder(existingMetadata)
+ .setEnabled(enabledState)
+ .build();
+ AppSearchBatchResult<String, Void> putDocumentBatchResult =
+ runtimeMetadataSearchSession
+ .put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(newMetadata)
+ .build())
+ .get();
+ if (!putDocumentBatchResult.isSuccess()) {
+ throw new IllegalStateException(
+ "Failed writing updated doc to AppSearch due to " + putDocumentBatchResult);
+ }
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private AppFunctionRuntimeMetadata getRuntimeMetadataGenericDocument(
+ @NonNull String packageName,
+ @NonNull String functionId,
+ @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
+ throws Exception {
+ String documentId =
+ AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(packageName, functionId);
+ GetByDocumentIdRequest request =
+ new GetByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE)
+ .addIds(documentId)
+ .build();
+ AppSearchBatchResult<String, GenericDocument> result =
+ runtimeMetadataSearchSession.getByDocumentId(request).get();
+ if (result.isSuccess()) {
+ return new AppFunctionRuntimeMetadata((result.getSuccesses().get(documentId)));
+ }
+ throw new IllegalArgumentException("Function " + functionId + " does not exist");
}
private void bindAppFunctionServiceUnchecked(
@NonNull ExecuteAppFunctionAidlRequest requestInternal,
@NonNull Intent serviceIntent,
@NonNull UserHandle targetUser,
+ @NonNull ICancellationSignal cancellationSignalTransport,
@NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
- int bindFlags) {
+ int bindFlags,
+ @NonNull IBinder callerBinder) {
+ CancellationSignal cancellationSignal =
+ CancellationSignal.fromTransport(cancellationSignalTransport);
+ ICancellationCallback cancellationCallback =
+ new ICancellationCallback.Stub() {
+ @Override
+ public void sendCancellationTransport(
+ @NonNull ICancellationSignal cancellationTransport) {
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+ };
boolean bindServiceResult =
mRemoteServiceCaller.runServiceCall(
serviceIntent,
bindFlags,
targetUser,
- new RunServiceCallCallback<IAppFunctionService>() {
- @Override
- public void onServiceConnected(
- @NonNull IAppFunctionService service,
- @NonNull
- ServiceUsageCompleteListener
- serviceUsageCompleteListener) {
- try {
- service.executeAppFunction(
- requestInternal.getClientRequest(),
- new IExecuteAppFunctionCallback.Stub() {
- @Override
- public void onResult(
- ExecuteAppFunctionResponse response) {
- safeExecuteAppFunctionCallback.onResult(
- response);
- serviceUsageCompleteListener.onCompleted();
- }
- });
- } catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_APP_UNKNOWN_ERROR,
- e.getMessage(),
- /* extras= */ null));
- serviceUsageCompleteListener.onCompleted();
- }
- }
-
- @Override
- public void onFailedToConnect() {
- Slog.e(TAG, "Failed to connect to service");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService",
- /* extras= */ null));
- }
- });
+ mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
+ cancellationSignal,
+ new RunAppFunctionServiceCallback(
+ requestInternal,
+ cancellationCallback,
+ safeExecuteAppFunctionCallback),
+ callerBinder);
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
- "Failed to bind the AppFunctionService.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "Failed to bind the AppFunctionService."));
}
}
- private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
+ private AppSearchManager getAppSearchManagerAsUser(@NonNull UserHandle userHandle) {
+ return mContext.createContextAsUser(userHandle, /* flags= */ 0)
+ .getSystemService(AppSearchManager.class);
+ }
+
+ private AppFunctionException mapExceptionToExecuteAppFunctionResponse(Throwable e) {
if (e instanceof CompletionException) {
e = e.getCause();
}
-
- if (e instanceof AppSearchException) {
- AppSearchException appSearchException = (AppSearchException) e;
- return ExecuteAppFunctionResponse.newFailure(
+ int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR;
+ if (e instanceof AppSearchException appSearchException) {
+ resultCode =
mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
- appSearchException.getResultCode()),
- appSearchException.getMessage(),
- /* extras= */ null);
+ appSearchException.getResultCode());
+ } else if (e instanceof SecurityException) {
+ resultCode = AppFunctionException.ERROR_DENIED;
+ } else if (e instanceof DisabledAppFunctionException) {
+ resultCode = AppFunctionException.ERROR_DISABLED;
}
-
- return ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- e.getMessage(),
- /* extras= */ null);
+ return new AppFunctionException(resultCode, e.getMessage());
}
private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -304,13 +496,13 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
switch (resultCode) {
case AppSearchResult.RESULT_NOT_FOUND:
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+ return AppFunctionException.ERROR_FUNCTION_NOT_FOUND;
case AppSearchResult.RESULT_INVALID_ARGUMENT:
case AppSearchResult.RESULT_INTERNAL_ERROR:
case AppSearchResult.RESULT_SECURITY_ERROR:
// fall-through
}
- return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+ return AppFunctionException.ERROR_SYSTEM_ERROR;
}
private void registerAppSearchObserver(@NonNull TargetUser user) {
@@ -322,8 +514,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
return;
}
FutureGlobalSearchSession futureGlobalSearchSession =
- new FutureGlobalSearchSession(
- perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR);
+ new FutureGlobalSearchSession(perUserAppSearchManager, THREAD_POOL_EXECUTOR);
AppFunctionMetadataObserver appFunctionMetadataObserver =
new AppFunctionMetadataObserver(
user.getUserHandle(),
@@ -362,6 +553,27 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
}
+ /**
+ * Retrieves the lock object associated with the given package name.
+ *
+ * <p>This method returns the lock object from the {@code mLocks} map if it exists. If no lock
+ * is found for the given package name, a new lock object is created, stored in the map, and
+ * returned.
+ */
+ @VisibleForTesting
+ @NonNull
+ Object getLockForPackage(String callingPackage) {
+ // Synchronized the access to mLocks to prevent race condition.
+ synchronized (mLocks) {
+ // By using a WeakHashMap, we allow the garbage collector to reclaim memory by removing
+ // entries associated with unused callingPackage keys. Therefore, we remove the null
+ // values before getting/computing a new value. The goal is to not let the size of this
+ // map grow without an upper bound.
+ mLocks.values().removeAll(Collections.singleton(null)); // Remove null values
+ return mLocks.computeIfAbsent(callingPackage, k -> new Object());
+ }
+ }
+
private static class AppFunctionMetadataObserver implements ObserverCallback {
@Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter;
@@ -408,4 +620,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
}
}
+
+ /** Throws when executing a disabled app function. */
+ private static class DisabledAppFunctionException extends RuntimeException {
+ private DisabledAppFunctionException(@NonNull String errorMessage) {
+ super(errorMessage);
+ }
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java
new file mode 100644
index 000000000000..b59915aa6343
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import static com.android.server.appfunctions.AppFunctionExecutors.LOGGING_THREAD_EXECUTOR;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/** Wraps AppFunctionsStatsLog. */
+public class AppFunctionsLoggerWrapper {
+ private static final String TAG = AppFunctionsLoggerWrapper.class.getSimpleName();
+
+ private static final int SUCCESS_RESPONSE_CODE = -1;
+
+ private final Context mContext;
+
+ public AppFunctionsLoggerWrapper(@NonNull Context context) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ void logAppFunctionSuccess(ExecuteAppFunctionAidlRequest request,
+ ExecuteAppFunctionResponse response, int callingUid) {
+ logAppFunctionsRequestReported(request, SUCCESS_RESPONSE_CODE,
+ response.getResponseDataSize(), callingUid);
+ }
+
+ void logAppFunctionError(ExecuteAppFunctionAidlRequest request, int errorCode, int callingUid) {
+ logAppFunctionsRequestReported(request, errorCode, /* responseSizeBytes = */ 0, callingUid);
+ }
+
+ private void logAppFunctionsRequestReported(ExecuteAppFunctionAidlRequest request,
+ int errorCode, int responseSizeBytes, int callingUid) {
+ final long latency = SystemClock.elapsedRealtime() - request.getRequestTime();
+ LOGGING_THREAD_EXECUTOR.execute(() -> AppFunctionsStatsLog.write(
+ AppFunctionsStatsLog.APP_FUNCTIONS_REQUEST_REPORTED,
+ callingUid,
+ getPackageUid(request.getClientRequest().getTargetPackageName()),
+ errorCode,
+ request.getClientRequest().getRequestDataSize(), responseSizeBytes,
+ latency)
+ );
+ }
+
+ private int getPackageUid(String packageName) {
+ try {
+ return mContext.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Package uid not found for " + packageName);
+ }
+ return 0;
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 3592ed587ab0..61917676e88d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -64,6 +64,9 @@ public interface CallerValidator {
* {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can
* still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}.
*
+ * @param callingUid The calling uid.
+ * @param callingPid The calling pid.
+ * @param targetUser The user which the caller is requesting to execute as.
* @param callerPackageName The calling package (as previously validated).
* @param targetPackageName The package that owns the app function to execute.
* @param functionId The id of the app function to execute.
@@ -72,15 +75,18 @@ public interface CallerValidator {
AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
+ @NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId);
/**
- * Checks if the user is organization managed.
+ * Checks if the app function policy is allowed.
*
+ * @param callingUser The current calling user.
* @param targetUser The user which the caller is requesting to execute as.
- * @return Whether the user is organization managed.
+ * @return Whether the app function policy is allowed.
*/
- boolean isUserOrganizationManaged(@NonNull UserHandle targetUser);
+ boolean verifyEnterprisePolicyIsAllowed(
+ @NonNull UserHandle callingUser, @NonNull UserHandle targetUser);
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 8b6251a59e3a..69481c32baf0 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -28,6 +28,7 @@ import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.AppFunctionsPolicy;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchManager.SearchContext;
@@ -39,7 +40,6 @@ import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
-import android.os.UserManager;
import com.android.internal.infra.AndroidFuture;
@@ -93,6 +93,7 @@ class CallerValidatorImpl implements CallerValidator {
public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
+ @NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId) {
@@ -122,7 +123,9 @@ class CallerValidatorImpl implements CallerValidator {
FutureAppSearchSession futureAppSearchSession =
new FutureAppSearchSessionImpl(
- mContext.getSystemService(AppSearchManager.class),
+ Objects.requireNonNull(
+ mContext.createContextAsUser(targetUser, 0)
+ .getSystemService(AppSearchManager.class)),
THREAD_POOL_EXECUTOR,
new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());
@@ -164,13 +167,16 @@ class CallerValidatorImpl implements CallerValidator {
}
@Override
- public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) {
- if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class))
- .isDeviceManaged()) {
- return true;
- }
- return Objects.requireNonNull(mContext.getSystemService(UserManager.class))
- .isManagedProfile(targetUser.getIdentifier());
+ public boolean verifyEnterprisePolicyIsAllowed(
+ @NonNull UserHandle callingUser, @NonNull UserHandle targetUser) {
+ @AppFunctionsPolicy
+ int callingUserPolicy = getDevicePolicyManagerAsUser(callingUser).getAppFunctionsPolicy();
+ @AppFunctionsPolicy
+ int targetUserPolicy = getDevicePolicyManagerAsUser(targetUser).getAppFunctionsPolicy();
+ boolean isSameUser = callingUser.equals(targetUser);
+
+ return isAppFunctionPolicyAllowed(targetUserPolicy, isSameUser)
+ && isAppFunctionPolicyAllowed(callingUserPolicy, isSameUser);
}
/**
@@ -260,4 +266,18 @@ class CallerValidatorImpl implements CallerValidator {
return Process.INVALID_UID;
}
}
+
+ private boolean isAppFunctionPolicyAllowed(
+ @AppFunctionsPolicy int userPolicy, boolean isSameUser) {
+ return switch (userPolicy) {
+ case DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY -> true;
+ case DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE -> isSameUser;
+ default -> false;
+ };
+ }
+
+ private DevicePolicyManager getDevicePolicyManagerAsUser(@NonNull UserHandle targetUser) {
+ return mContext.createContextAsUser(targetUser, /* flags= */ 0)
+ .getSystemService(DevicePolicyManager.class);
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index de2034b5be2f..b89348c038fa 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -39,20 +39,6 @@ import java.util.List;
/** A future API wrapper of {@link AppSearchSession} APIs. */
public interface FutureAppSearchSession extends Closeable {
- /** Converts a failed app search result codes into an exception. */
- @NonNull
- static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) {
- return switch (appSearchResult.getResultCode()) {
- case AppSearchResult.RESULT_INVALID_ARGUMENT ->
- new IllegalArgumentException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_IO_ERROR ->
- new IOException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_SECURITY_ERROR ->
- new SecurityException(appSearchResult.getErrorMessage());
- default -> new IllegalStateException(appSearchResult.getErrorMessage());
- };
- }
-
/**
* Sets the schema that represents the organizational structure of data within the AppSearch
* database.
@@ -86,17 +72,4 @@ public interface FutureAppSearchSession extends Closeable {
@Override
void close();
-
- /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
- interface FutureSearchResults {
-
- /**
- * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession}
- * database.
- *
- * <p>Continue calling this method to access results until it returns an empty list,
- * signifying there are no more results.
- */
- AndroidFuture<List<SearchResult>> getNextPage();
- }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
index d24bb871c393..87589f51cf1b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
@@ -16,7 +16,7 @@
package com.android.server.appfunctions;
-import static com.android.server.appfunctions.FutureAppSearchSession.failedResultToException;
+import static com.android.server.appfunctions.FutureSearchResults.failedResultToException;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
@@ -192,33 +192,6 @@ public class FutureAppSearchSessionImpl implements FutureAppSearchSession {
});
}
- private static final class FutureSearchResultsImpl implements FutureSearchResults {
- private final SearchResults mSearchResults;
- private final Executor mExecutor;
-
- private FutureSearchResultsImpl(
- @NonNull SearchResults searchResults, @NonNull Executor executor) {
- this.mSearchResults = searchResults;
- this.mExecutor = executor;
- }
-
- @Override
- public AndroidFuture<List<SearchResult>> getNextPage() {
- AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
- new AndroidFuture<>();
-
- mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
- return nextPageFuture.thenApply(
- result -> {
- if (result.isSuccess()) {
- return result.getResultValue();
- } else {
- throw new RuntimeException(failedResultToException(result));
- }
- });
- }
- }
-
private static final class BatchResultCallbackAdapter<K, V>
implements BatchResultCallback<K, V> {
private final AndroidFuture<AppSearchBatchResult<K, V>> mFuture;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java
index 874c5daa1662..4cc08173ac97 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
+import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.observer.ObserverCallback;
import android.app.appsearch.observer.ObserverSpec;
@@ -49,12 +50,23 @@ public class FutureGlobalSearchSession implements Closeable {
return result.getResultValue();
} else {
throw new RuntimeException(
- FutureAppSearchSession.failedResultToException(result));
+ FutureSearchResults.failedResultToException(result));
}
});
}
/**
+ * Retrieves documents from the open {@link GlobalSearchSession} that match a given query string
+ * and type of search provided.
+ */
+ public AndroidFuture<FutureSearchResults> search(
+ String queryExpression, SearchSpec searchSpec) {
+ return getSessionAsync()
+ .thenApply(session -> session.search(queryExpression, searchSpec))
+ .thenApply(result -> new FutureSearchResultsImpl(result, mExecutor));
+ }
+
+ /**
* Registers an observer callback for the given target package name.
*
* @param targetPackageName The package name of the target app.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
new file mode 100644
index 000000000000..c38ff143178f
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+/** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
+public interface FutureSearchResults extends Closeable {
+
+ /** Converts a failed app search result codes into an exception. */
+ @NonNull
+ public static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) {
+ return switch (appSearchResult.getResultCode()) {
+ case AppSearchResult.RESULT_INVALID_ARGUMENT ->
+ new IllegalArgumentException(appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_IO_ERROR ->
+ new IOException(appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_SECURITY_ERROR ->
+ new SecurityException(appSearchResult.getErrorMessage());
+ default -> new IllegalStateException(appSearchResult.getErrorMessage());
+ };
+ }
+
+ /**
+ * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession}
+ * database.
+ *
+ * <p>Continue calling this method to access results until it returns an empty list, signifying
+ * there are no more results.
+ */
+ AndroidFuture<List<SearchResult>> getNextPage();
+
+ @Override
+ void close();
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
new file mode 100644
index 000000000000..c8bc538f7226
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class FutureSearchResultsImpl implements FutureSearchResults {
+ private final SearchResults mSearchResults;
+ private final Executor mExecutor;
+
+ public FutureSearchResultsImpl(
+ @NonNull SearchResults searchResults, @NonNull Executor executor) {
+ this.mSearchResults = searchResults;
+ this.mExecutor = executor;
+ }
+
+ @Override
+ public AndroidFuture<List<SearchResult>> getNextPage() {
+ AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture = new AndroidFuture<>();
+
+ mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
+ return nextPageFuture
+ .thenApply(
+ result -> {
+ if (result.isSuccess()) {
+ return result.getResultValue();
+ } else {
+ throw new RuntimeException(
+ FutureSearchResults.failedResultToException(result));
+ }
+ });
+ }
+
+ @Override
+ public void close() {
+ mSearchResults.close();
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index d84b20556053..cc73288cdbfa 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -45,7 +45,6 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
-import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -85,7 +84,9 @@ public class MetadataSyncAdapter {
@NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
mPackageManager = Objects.requireNonNull(packageManager);
mAppSearchManager = Objects.requireNonNull(appSearchManager);
- mExecutor = Executors.newSingleThreadExecutor();
+ mExecutor =
+ Executors.newSingleThreadExecutor(
+ new NamedThreadFactory("AppFunctionSyncExecutors"));
}
/**
@@ -421,26 +422,29 @@ public class MetadataSyncAdapter {
Objects.requireNonNull(propertyPackageName);
ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>();
- FutureSearchResults futureSearchResults =
+ try (FutureSearchResults futureSearchResults =
searchSession
.search(
"",
buildMetadataSearchSpec(
schemaType, propertyFunctionId, propertyPackageName))
- .get();
- List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
- // TODO(b/357551503): This could be expensive if we have more functions
- while (!searchResultsList.isEmpty()) {
- for (SearchResult searchResult : searchResultsList) {
- String packageName =
- searchResult.getGenericDocument().getPropertyString(propertyPackageName);
- String functionId =
- searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
- packageToFunctionIds
- .computeIfAbsent(packageName, k -> new ArraySet<>())
- .add(functionId);
+ .get(); ) {
+ List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
+ // TODO(b/357551503): This could be expensive if we have more functions
+ while (!searchResultsList.isEmpty()) {
+ for (SearchResult searchResult : searchResultsList) {
+ String packageName =
+ searchResult
+ .getGenericDocument()
+ .getPropertyString(propertyPackageName);
+ String functionId =
+ searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
+ packageToFunctionIds
+ .computeIfAbsent(packageName, k -> new ArraySet<>())
+ .add(functionId);
+ }
+ searchResultsList = futureSearchResults.getNextPage().get();
}
- searchResultsList = futureSearchResults.getNextPage().get();
}
return packageToFunctionIds;
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java b/services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java
new file mode 100644
index 000000000000..7adcc48a6a35
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** A {@link ThreadFactory} that creates threads with a given base name. */
+public class NamedThreadFactory implements ThreadFactory {
+ private final ThreadFactory mDefaultThreadFactory;
+ private final String mBaseName;
+ private final AtomicInteger mCount = new AtomicInteger(0);
+
+ public NamedThreadFactory(final String baseName) {
+ mDefaultThreadFactory = Executors.defaultThreadFactory();
+ mBaseName = baseName;
+ }
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ final Thread thread = mDefaultThreadFactory.newThread(runnable);
+ thread.setName(mBaseName + "-" + mCount.getAndIncrement());
+ return thread;
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java
index cd5c3831bc0d..d0e858e71de4 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java
@@ -17,41 +17,56 @@ package com.android.server.appfunctions;
import android.annotation.NonNull;
import android.content.Intent;
+import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.UserHandle;
/**
- * Defines a contract for establishing temporary connections to services and executing operations
- * within a specified timeout. Implementations of this interface provide mechanisms to ensure that
- * services are properly unbound after the operation completes or a timeout occurs.
+ * Defines a contract for establishing temporary connections to services and executing operations.
+ * Implementations of this interface provide mechanisms to ensure that services are properly unbound
+ * after the operation completes or a cancellation timeout occurs.
*
* @param <T> Class of wrapped service.
*/
public interface RemoteServiceCaller<T> {
/**
- * Initiates service binding and executes a provided method when the service connects. Unbinds
- * the service after execution or upon timeout. Returns the result of the bindService API.
+ * Initiates service binding and executes a provided method when the service connects.
+ *
+ * <p>Unbinds the service after execution or upon cancellation timeout or calling process death.
+ * Returns the result of the bindService API.
*
* <p>When the service connection was made successfully, it's the caller responsibility to
* report the usage is completed and can be unbound by calling {@link
* ServiceUsageCompleteListener#onCompleted()}.
*
- * <p>This method includes a timeout mechanism to prevent the system from being stuck in a state
- * where a service is bound indefinitely (for example, if the binder method never returns). This
- * helps ensure that the calling app does not remain alive unnecessarily.
+ * <p>This method includes a timeout mechanism for cancellation to prevent the system from being
+ * stuck in a state where a service is bound indefinitely. If the app to be bound does not
+ * return the result within `cancellationTimeoutMillis` after the cancellation signal is sent,
+ * this method will unbind the service connection.
+ *
+ * <p>This method will also unbind the service after the calling process dies (because a
+ * cancellation signal cannot be sent and system server can become bound forever if otherwise).
*
* @param intent An Intent object that describes the service that should be bound.
* @param bindFlags Flags used to control the binding process See {@link
* android.content.Context#bindService}.
* @param userHandle The UserHandle of the user for which the service should be bound.
+ * @param cancellationTimeoutMillis The timeout before the service is unbound after a
+ * cancellation signal is issued.
+ * @param cancellationSignal The cancellation signal forwarded to the service.
* @param callback A callback to be invoked for various events. See {@link
* RunServiceCallCallback}.
+ * @param callerBinder The binder of the caller.
*/
boolean runServiceCall(
@NonNull Intent intent,
int bindFlags,
@NonNull UserHandle userHandle,
- @NonNull RunServiceCallCallback<T> callback);
+ long cancellationTimeoutMillis,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull RunServiceCallCallback<T> callback,
+ @NonNull IBinder callerBinder);
/** An interface for clients to signal that they have finished using a bound service. */
interface ServiceUsageCompleteListener {
@@ -73,5 +88,8 @@ public interface RemoteServiceCaller<T> {
/** Called when the service connection was failed to establish. */
void onFailedToConnect();
+
+ /** Called when the caller has cancelled this remote service call. */
+ void onCancelled();
}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
index 070a99d5bb28..15081598c301 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
@@ -16,15 +16,19 @@
package com.android.server.appfunctions;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Slog;
import java.util.concurrent.Executor;
import java.util.function.Function;
@@ -63,10 +67,19 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> {
@NonNull Intent intent,
int bindFlags,
@NonNull UserHandle userHandle,
- @NonNull RunServiceCallCallback<T> callback) {
+ long cancellationTimeoutMillis,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull RunServiceCallCallback<T> callback,
+ @NonNull IBinder callerBinder) {
OneOffServiceConnection serviceConnection =
new OneOffServiceConnection(
- intent, bindFlags, userHandle, callback);
+ intent,
+ bindFlags,
+ userHandle,
+ cancellationTimeoutMillis,
+ cancellationSignal,
+ callback,
+ callerBinder);
return serviceConnection.bindAndRun();
}
@@ -77,23 +90,52 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> {
private final int mFlags;
private final UserHandle mUserHandle;
private final RunServiceCallCallback<T> mCallback;
+ private final long mCancellationTimeoutMillis;
+ private final CancellationSignal mCancellationSignal;
+ private final Runnable mCancellationTimeoutRunnable;
+ private final IBinder mCallerBinder;
+ @Nullable private IBinder.DeathRecipient mDirectServiceVulture;
OneOffServiceConnection(
@NonNull Intent intent,
int flags,
@NonNull UserHandle userHandle,
- @NonNull RunServiceCallCallback<T> callback) {
+ long cancellationTimeoutMillis,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull RunServiceCallCallback<T> callback,
+ @NonNull IBinder callerBinder) {
mIntent = intent;
mFlags = flags;
mCallback = callback;
mUserHandle = userHandle;
+ mCancellationTimeoutMillis = cancellationTimeoutMillis;
+ mCancellationSignal = cancellationSignal;
+ mCancellationTimeoutRunnable = this::safeUnbind;
+ mCallerBinder = callerBinder;
}
public boolean bindAndRun() {
boolean bindServiceResult =
mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
- if(!bindServiceResult) {
+ if (bindServiceResult) {
+ mCancellationSignal.setOnCancelListener(
+ () -> {
+ mCallback.onCancelled();
+ mHandler.postDelayed(
+ mCancellationTimeoutRunnable, mCancellationTimeoutMillis);
+ });
+ mDirectServiceVulture =
+ () -> {
+ Slog.w(TAG, "Caller process onDeath signal received");
+ mCancellationSignal.cancel();
+ };
+ try {
+ mCallerBinder.linkToDeath(mDirectServiceVulture, /* flags= */ 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to link to death on " + mCallerBinder + ": ", e);
+ }
+ } else {
safeUnbind();
}
@@ -127,7 +169,11 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> {
private void safeUnbind() {
try {
+ mHandler.removeCallbacks(mCancellationTimeoutRunnable);
mContext.unbindService(this);
+ if (mDirectServiceVulture != null) {
+ mCallerBinder.unlinkToDeath(mDirectServiceVulture, 0);
+ }
} catch (Exception ex) {
Log.w(TAG, "Failed to unbind", ex);
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
new file mode 100644
index 000000000000..896c0fc51683
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.AppFunctionException;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.util.Slog;
+
+import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
+import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+
+/**
+ * A callback to forward a request to the {@link IAppFunctionService} and report back the result.
+ */
+public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAppFunctionService> {
+ private static final String TAG = RunAppFunctionServiceCallback.class.getSimpleName();
+
+ private final ExecuteAppFunctionAidlRequest mRequestInternal;
+ private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
+ private final ICancellationCallback mCancellationCallback;
+
+ public RunAppFunctionServiceCallback(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ this.mRequestInternal = requestInternal;
+ this.mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
+ this.mCancellationCallback = cancellationCallback;
+ }
+
+ @Override
+ public void onServiceConnected(
+ @NonNull IAppFunctionService service,
+ @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener) {
+ try {
+ service.executeAppFunction(
+ mRequestInternal.getClientRequest(),
+ mRequestInternal.getCallingPackage(),
+ mCancellationCallback,
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onSuccess(ExecuteAppFunctionResponse response) {
+ mSafeExecuteAppFunctionCallback.onResult(response);
+ serviceUsageCompleteListener.onCompleted();
+ }
+
+ @Override
+ public void onError(AppFunctionException error) {
+ mSafeExecuteAppFunctionCallback.onError(error);
+ serviceUsageCompleteListener.onCompleted();
+ }
+ });
+ } catch (Exception e) {
+ mSafeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+ e.getMessage()));
+ serviceUsageCompleteListener.onCompleted();
+ }
+ }
+
+ @Override
+ public void onFailedToConnect() {
+ Slog.e(TAG, "Failed to connect to service");
+ mSafeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService"));
+ }
+
+ @Override
+ public void onCancelled() {
+ mSafeExecuteAppFunctionCallback.disable();
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
index caa4bf0e9d27..8d2d1b2dfd00 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
@@ -21,6 +21,9 @@ public interface ServiceConfig {
// TODO(b/357551503): Obtain namespace from DeviceConfig.
String NAMESPACE_APP_FUNCTIONS = "appfunctions";
- /** Returns the maximum time to wait for an app function execution to be complete. */
- long getExecuteAppFunctionTimeoutMillis();
+ /**
+ * Returns the timeout for which the system server waits for the app function service to
+ * successfully cancel the execution of an app function before forcefully unbinding the service.
+ */
+ long getExecuteAppFunctionCancellationTimeoutMillis();
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
index f18789be5ce8..787f52afda4b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
@@ -20,15 +20,16 @@ import android.provider.DeviceConfig;
/** Implementation of {@link ServiceConfig} */
public class ServiceConfigImpl implements ServiceConfig {
- static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT =
- "execute_app_function_timeout_millis";
- static final long DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS = 5000L;
+ static final String DEVICE_CONFIG_PROPERTY_EXECUTION_CANCELLATION_TIMEOUT =
+ "execute_app_function_cancellation_timeout_millis";
+ static final long DEFAULT_EXECUTE_APP_FUNCTION_CANCELLATION_TIMEOUT_MS = 5000L;
+
@Override
- public long getExecuteAppFunctionTimeoutMillis() {
+ public long getExecuteAppFunctionCancellationTimeoutMillis() {
return DeviceConfig.getLong(
NAMESPACE_APP_FUNCTIONS,
- DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT,
- DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS);
+ DEVICE_CONFIG_PROPERTY_EXECUTION_CANCELLATION_TIMEOUT,
+ DEFAULT_EXECUTE_APP_FUNCTION_CANCELLATION_TIMEOUT_MS);
}
}
diff --git a/services/appfunctions/lint-baseline.xml b/services/appfunctions/lint-baseline.xml
new file mode 100644
index 000000000000..fbcb9f3e6ec6
--- /dev/null
+++ b/services/appfunctions/lint-baseline.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="executeAppFunction should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java"
+ line="101"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="onResult should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java"
+ line="243"
+ column="49"/>
+ </issue>
+
+</issues>
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 17e9997e8883..cffdfbd36532 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,8 +16,11 @@
package com.android.server.appwidget;
+import static android.appwidget.flags.Flags.checkRemoteViewsUriPermission;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.appwidget.flags.Flags.remoteViewsProto;
import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
+import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers;
import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -30,8 +33,10 @@ import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.PermissionName;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -58,6 +63,7 @@ import android.appwidget.AppWidgetProviderInfo;
import android.appwidget.PendingHostUpdate;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.FilterComparison;
@@ -102,6 +108,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.GeneratedPreviewsProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -120,7 +127,9 @@ import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import android.view.Display;
import android.view.View;
import android.widget.RemoteViews;
@@ -132,6 +141,7 @@ import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -142,6 +152,8 @@ import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.WidgetBackupProvider;
+import com.android.server.uri.GrantUri;
+import com.android.server.uri.UriGrantsManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -179,11 +191,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Simple flag to enable/disable debug logging.
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
- // String constants for XML schema migration related to changes in keyguard package.
- private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
- private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
- private static final int KEYGUARD_HOST_ID = 0x4b455947;
-
// Filename for app widgets state persisted on disk.
private static final String STATE_FILENAME = "appwidgets.xml";
@@ -203,6 +210,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private static final int UNKNOWN_USER_ID = -10;
// Version of XML schema for app widgets. Bump if the stored widgets need to be upgraded.
+ // Version 1 introduced in 2014 - Android 5.0
private static final int CURRENT_VERSION = 1;
// Every widget update request is associated which an increasing sequence number. This is
@@ -219,6 +227,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// XML attribute for widget ids that are pending deletion.
// See {@link Provider#pendingDeletedWidgetIds}.
private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
+ // Name of service directory in /data/system_ce/<user>/
+ private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget";
+ // Name of previews directory in /data/system_ce/<user>/appwidget/
+ private static final String WIDGET_PREVIEWS_DIRNAME = "previews";
// Hard limit of number of hosts an app can create, note that the app that hosts the widgets
// can have multiple instances of {@link AppWidgetHost}, typically in respect to different
@@ -318,6 +330,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Handler to the background thread that saves states to disk.
private Handler mSaveStateHandler;
+
+ private Handler mAlarmHandler;
+ // Handler to the background thread that saves generated previews to disk. All operations that
+ // modify saved previews must be run on this Handler.
+ private Handler mSavePreviewsHandler;
// Handler to the foreground thread that handles broadcasts related to user
// and package events, as well as various internal events within
// AppWidgetService.
@@ -358,9 +375,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (removeAppWidgetServiceIoFromCriticalPath()) {
mSaveStateHandler = new Handler(BackgroundThread.get().getLooper(),
this::handleSaveMessage);
+ mAlarmHandler = new Handler(BackgroundThread.get().getLooper());
} else {
mSaveStateHandler = BackgroundThread.getHandler();
}
+ mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper());
final ServiceThread serviceThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
serviceThread.start();
@@ -380,7 +399,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS);
mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
- generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders);
+ generatedPreviewMaxCallsPerInterval,
+ // Set a limit on the number of providers if storing them in memory.
+ remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders);
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
@@ -593,7 +614,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// ... and see if these are hosts we've been awaiting.
// NOTE: We are backing up and restoring only the owner.
// TODO: http://b/22388012
- if (newPackageAdded && userId == UserHandle.USER_SYSTEM) {
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (newPackageAdded && mainUser != null && userId == mainUser.getIdentifier()) {
final int uid = getUidForPackage(pkgName, userId);
if (uid >= 0 ) {
resolveHostUidLocked(pkgName, uid);
@@ -646,7 +668,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
for (int i = 0; i < providerCount; i++) {
Provider provider = mProviders.get(i);
if (provider.id.uid == clearedUid) {
- changed |= provider.clearGeneratedPreviewsLocked();
+ if (remoteViewsProto()) {
+ changed |= clearGeneratedPreviewsAsync(provider);
+ } else {
+ changed |= provider.clearGeneratedPreviewsLocked();
+ }
+ if (DEBUG) {
+ Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed);
+ }
}
}
return changed;
@@ -896,18 +925,30 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
for (int j = 0; j < widgetCount; j++) {
Widget widget = provider.widgets.get(j);
if (targetWidget != null && targetWidget != widget) continue;
+ // Identify the user in the host process since the intent will be invoked by
+ // the host app.
+ final Host host = widget.host;
+ final UserHandle hostUser;
+ if (host != null && host.id != null) {
+ hostUser = UserHandle.getUserHandleForUid(host.id.uid);
+ } else {
+ // Fallback to the parent profile if the host is null.
+ Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId);
+ hostUser = mUserManager.getProfileParent(appUserId).getUserHandle();
+ }
if (provider.maskedByStoppedPackage) {
Intent intent = createUpdateIntentLocked(provider,
new int[] { widget.appWidgetId });
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getBroadcast(mContext, widget.appWidgetId,
+ PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ | PendingIntent.FLAG_IMMUTABLE, hostUser));
} else if (onClickIntent != null) {
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ PendingIntent.getActivityAsUser(mContext, widget.appWidgetId,
+ onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_IMMUTABLE, null /* options */,
+ hostUser));
}
if (widget.replaceWithMaskedViewsLocked(views)) {
scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
@@ -1446,7 +1487,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
* in {@link #allocateAppWidgetId}.
*
* @param callingPackage The package that calls this method.
- * @param appWidgetId The id of theapp widget to bind.
+ * @param appWidgetId The id of the widget to bind.
* @param providerProfileId The user/profile id of the provider.
* @param providerComponent The {@link ComponentName} that provides the widget.
* @param options The options to pass to the provider.
@@ -1471,9 +1512,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
return false;
}
- // If the provider is not under the calling user, make sure this
- // provider is allowlisted for access from the parent.
- if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ // If the provider is not under the calling user, make sure this provider is allowlisted for
+ // access from the parent, or that the caller has permission to interact across users.
+ if (!mSecurityPolicy.canAccessProvider(
providerComponent.getPackageName(), providerProfileId)) {
return false;
}
@@ -1738,6 +1779,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
return false;
}
+ /**
+ * Called by a {@link AppWidgetHost} to remove all records (i.e. {@link Host}
+ * and all {@link Widget} associated with the host) from a specified host.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param hostId id of the {@link Host}.
+ * @see AppWidgetHost#deleteHost()
+ */
@Override
public void deleteHost(String callingPackage, int hostId) {
final int userId = UserHandle.getCallingUserId();
@@ -1771,6 +1820,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Called by a host process to remove all records (i.e. {@link Host}
+ * and all {@link Widget} associated with the host) from all hosts associated
+ * with the calling process.
+ *
+ * Typically used in clean up after test execution.
+ *
+ * @see AppWidgetHost#deleteAllHosts()
+ */
@Override
public void deleteAllHosts() {
final int userId = UserHandle.getCallingUserId();
@@ -1805,6 +1863,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Returns the {@link AppWidgetProviderInfo} for the specified AppWidget.
+ *
+ * Typically used by launcher during the restore of an AppWidget, the binding
+ * of new AppWidget, and during grid size migration.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @return The {@link AppWidgetProviderInfo} for the specified widget.
+ *
+ * @see AppWidgetManager#getAppWidgetInfo(int)
+ */
@Override
public AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
@@ -1859,6 +1929,17 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Returns the most recent {@link RemoteViews} of the specified AppWidget.
+ * Typically serves as a cache of the content of the AppWidget.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @return The {@link RemoteViews} of the specified widget.
+ *
+ * @see AppWidgetHost#updateAppWidgetDeferred(String, int)
+ * @see AppWidgetHost#setListener(int, AppWidgetHostListener)
+ */
@Override
public RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
@@ -1886,6 +1967,29 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Update the extras for a given widget instance.
+ * <p>
+ * The extras can be used to embed additional information about this widget to be accessed
+ * by the associated widget's AppWidgetProvider.
+ *
+ * <p>
+ * The new options are merged into existing options using {@link Bundle#putAll} semantics.
+ *
+ * <p>
+ * Typically called by a {@link AppWidgetHost} (e.g. Launcher) to notify
+ * {@link AppWidgetProvider} regarding contextual changes (e.g. sizes) when rendering the
+ * widget.
+ * Calling this method would trigger onAppWidgetOptionsChanged() callback on the provider's
+ * side.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @param options New options associate with this widget.
+ *
+ * @see AppWidgetManager#getAppWidgetOptions(int, Bundle)
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
+ */
@Override
public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) {
final int userId = UserHandle.getCallingUserId();
@@ -1919,6 +2023,21 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Get the extras associated with a given widget instance.
+ * <p>
+ * The extras can be used to embed additional information about this widget to be accessed
+ * by the associated widget's AppWidgetProvider.
+ *
+ * Typically called by a host process (e.g. Launcher) to determine if they need to update the
+ * options of the widget.
+ *
+ * @see #updateAppWidgetOptions(String, int, Bundle)
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @return The options associated with the specified widget instance.
+ */
@Override
public Bundle getAppWidgetOptions(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
@@ -1946,6 +2065,28 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Updates the content of the widgets (as specified by appWidgetIds) using the provided
+ * {@link RemoteViews}.
+ *
+ * Typically called by the provider's process. Either in response to the invocation of
+ * {@link AppWidgetProvider#onUpdate} or upon receiving the
+ * {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast.
+ *
+ * <p>
+ * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+ * contain a complete representation of the widget. For performing partial widget updates, see
+ * {@link #partiallyUpdateAppWidgetIds(String, int[], RemoteViews)}.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetIds Ids of the widgets to be updated.
+ * @param views The RemoteViews object containing the update.
+ *
+ * @see AppWidgetProvider#onUpdate(Context, AppWidgetManager, int[])
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ * @see AppWidgetManager#updateAppWidget(int, RemoteViews)
+ * @see AppWidgetManager#updateAppWidget(int[], RemoteViews)
+ */
@Override
public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views) {
@@ -1956,6 +2097,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
}
+ /**
+ * Perform an incremental update or command on the widget(s) specified by appWidgetIds.
+ * <p>
+ * This update differs from {@link #updateAppWidgetIds(int[], RemoteViews)} in that the
+ * RemoteViews object which is passed is understood to be an incomplete representation of the
+ * widget, and hence does not replace the cached representation of the widget. As of API
+ * level 17, the new properties set within the views objects will be appended to the cached
+ * representation of the widget, and hence will persist.
+ *
+ * <p>
+ * This method will be ignored if a widget has not received a full update via
+ * {@link #updateAppWidget(int[], RemoteViews)}.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetIds Ids of the widgets to be updated.
+ * @param views The RemoteViews object containing the incremental update / command.
+ *
+ * @see AppWidgetManager#partiallyUpdateAppWidget(int[], RemoteViews)
+ * @see RemoteViews#setDisplayedChild(int, int)
+ * @see RemoteViews#setScrollPosition(int, int)
+ */
@Override
public void partiallyUpdateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views) {
@@ -1966,6 +2128,24 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
updateAppWidgetIds(callingPackage, appWidgetIds, views, true);
}
+ /**
+ * Callback function which marks specified providers as extended from AppWidgetProvider.
+ *
+ * This information is used to determine if the system can combine
+ * {@link AppWidgetManager#ACTION_APPWIDGET_ENABLED} and
+ * {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} into a single broadcast.
+ *
+ * Note: The system can only combine the two broadcasts if the provider is extended from
+ * AppWidgetProvider. When they do, they are expected to override the
+ * {@link AppWidgetProvider#onUpdate} callback function to provide updates, as opposed to
+ * listening for {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcasts directly.
+ *
+ * @see AppWidgetManager#ACTION_APPWIDGET_ENABLED
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ * @see AppWidgetManager#ACTION_APPWIDGET_ENABLE_AND_UPDATE
+ * @see AppWidgetProvider#onReceive(Context, Intent)
+ * @see #sendEnableAndUpdateIntentLocked
+ */
@Override
public void notifyProviderInheritance(@Nullable final ComponentName[] componentNames) {
final int userId = UserHandle.getCallingUserId();
@@ -2000,6 +2180,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Notifies the specified collection view in all the specified AppWidget instances
+ * to invalidate their data.
+ *
+ * This method is effectively deprecated since
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} has been deprecated.
+ *
+ * @see AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int)
+ */
@Override
public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
int viewId) {
@@ -2035,6 +2224,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Updates the content of all widgets associated with given provider (as specified by
+ * componentName) using the provided {@link RemoteViews}.
+ *
+ * Typically called by the provider's process when there's an update that needs to be supplied
+ * to all instances of the widgets.
+ *
+ * @param componentName The component name of the provider.
+ * @param views The RemoteViews object containing the update.
+ *
+ * @see AppWidgetManager#updateAppWidget(ComponentName, RemoteViews)
+ */
@Override
public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
final int userId = UserHandle.getCallingUserId();
@@ -2068,6 +2269,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Updates the info for the supplied AppWidget provider. Apps can use this to change the default
+ * behavior of the widget based on the state of the app (e.g., if the user is logged in
+ * or not). Calling this API completely replaces the previous definition.
+ *
+ * <p>
+ * The manifest entry of the provider should contain an additional meta-data tag similar to
+ * {@link AppWidgetManager#META_DATA_APPWIDGET_PROVIDER} which should point to any alternative
+ * definitions for the provider.
+ *
+ * <p>
+ * This is persisted across device reboots and app updates. If this meta-data key is not
+ * present in the manifest entry, the info reverts to default.
+ *
+ * @param provider {@link ComponentName} for the {@link
+ * android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+ * @param metaDataKey key for the meta-data tag pointing to the new provider info. Use null
+ * to reset any previously set info.
+ *
+ * @see AppWidgetManager#updateAppWidgetProviderInfo(ComponentName, String)
+ */
@Override
public void updateAppWidgetProviderInfo(ComponentName componentName, String metadataKey) {
final int userId = UserHandle.getCallingUserId();
@@ -2119,6 +2341,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Returns true if the default launcher app on the device (the one that currently
+ * holds the android.app.role.HOME role) can support pinning widgets
+ * (typically means adding widgets into home screen).
+ */
@Override
public boolean isRequestPinAppWidgetSupported() {
synchronized (mLock) {
@@ -2133,6 +2360,44 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
}
+ /**
+ * Request to pin an app widget on the current launcher. It's up to the launcher to accept this
+ * request (optionally showing a user confirmation). If the request is accepted, the caller will
+ * get a confirmation with extra {@link #EXTRA_APPWIDGET_ID}.
+ *
+ * <p>When a request is denied by the user, the caller app will not get any response.
+ *
+ * <p>Only apps with a foreground activity or a foreground service can call it. Otherwise
+ * it'll throw {@link IllegalStateException}.
+ *
+ * <p>It's up to the launcher how to handle previous pending requests when the same package
+ * calls this API multiple times in a row. It may ignore the previous requests,
+ * for example.
+ *
+ * <p>Launcher will not show the configuration activity associated with the provider in this
+ * case. The app could either show the configuration activity as a response to the callback,
+ * or show if before calling the API (various configurations can be encapsulated in
+ * {@code successCallback} to avoid persisting them before the widgetId is known).
+ *
+ * @param provider The {@link ComponentName} for the {@link
+ * android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+ * @param extras If not null, this is passed to the launcher app. For eg {@link
+ * #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview.
+ * @param successCallback If not null, this intent will be sent when the widget is created.
+ *
+ * @return {@code TRUE} if the launcher supports this feature. Note the API will return without
+ * waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
+ * the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature or if
+ * calling app belongs to a user-profile with items restricted on home screen.
+ *
+ * @see android.content.pm.ShortcutManager#isRequestPinShortcutSupported()
+ * @see android.content.pm.ShortcutManager#requestPinShortcut(ShortcutInfo, IntentSender)
+ * @see AppWidgetManager#isRequestPinAppWidgetSupported()
+ * @see AppWidgetManager#requestPinAppWidget(ComponentName, Bundle, PendingIntent)
+ *
+ * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
+ * service or when the user is locked.
+ */
@Override
public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
Bundle extras, IntentSender resultSender) {
@@ -2184,6 +2449,24 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
+ /**
+ * Gets the AppWidget providers for the given user profile. User profile can only
+ * be the current user or a profile of the current user. For example, the current
+ * user may have a corporate profile. In this case the parent user profile has a
+ * child profile, the corporate one.
+ *
+ * @param categoryFilter Will only return providers which register as any of the specified
+ * specified categories. See {@link AppWidgetProviderInfo#widgetCategory}.
+ * @param profile A profile of the current user which to be queried. The user
+ * is itself also a profile. If null, the providers only for the current user
+ * are returned.
+ * @param packageName If specified, will only return providers from the given package.
+ * @return The installed providers.
+ *
+ * @see android.os.Process#myUserHandle()
+ * @see android.os.UserManager#getUserProfiles()
+ * @see AppWidgetManager#getInstalledProvidersForProfile(int, UserHandle, String)
+ */
@Override
public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
int profileId, String packageName) {
@@ -2230,7 +2513,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Add providers only for the requested profile that are allowlisted.
final int providerProfileId = info.getProfile().getIdentifier();
if (providerProfileId == profileId
- && mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ && mSecurityPolicy.canAccessProvider(
providerPackageName, providerProfileId)
&& !mPackageManagerInternal.filterAppAccess(providerPackageName, callingUid,
profileId)) {
@@ -2242,6 +2525,26 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Updates the content of the widgets (as specified by appWidgetIds) using the provided
+ * {@link RemoteViews}.
+ *
+ * If performing a partial update, the given RemoteViews object is merged into existing
+ * RemoteViews object.
+ *
+ * Fails silently if appWidgetIds is null or empty, or cannot found a widget with the given
+ * appWidgetId.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetIds Ids of the widgets to be updated.
+ * @param views The RemoteViews object containing the update.
+ * @param partially Whether it was a partial update.
+ *
+ * @see AppWidgetProvider#onUpdate(Context, AppWidgetManager, int[])
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ * @see AppWidgetManager#updateAppWidget(int, RemoteViews)
+ * @see AppWidgetManager#updateAppWidget(int[], RemoteViews)
+ */
private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views, boolean partially) {
final int userId = UserHandle.getCallingUserId();
@@ -2252,6 +2555,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Make sure the package runs under the caller uid.
mSecurityPolicy.enforceCallFromPackage(callingPackage);
+ // Make sure RemoteViews do not contain URIs that the caller cannot access.
+ if (checkRemoteViewsUriPermission()) {
+ checkRemoteViewsUris(views);
+ }
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
@@ -2271,12 +2578,62 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * Checks that all of the Uris in the given RemoteViews are accessible to the caller.
+ */
+ private void checkRemoteViewsUris(RemoteViews views) {
+ UriGrantsManagerInternal uriGrantsManager = LocalServices.getService(
+ UriGrantsManagerInternal.class);
+ int callingUid = Binder.getCallingUid();
+ int callingUser = UserHandle.getCallingUserId();
+ views.visitUris(uri -> {
+ switch (uri.getScheme()) {
+ // Check that content:// URIs are accessible to the caller.
+ case ContentResolver.SCHEME_CONTENT:
+ boolean canAccessUri = uriGrantsManager.checkUriPermission(
+ GrantUri.resolve(callingUser, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION), callingUid,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ /* isFullAccessForContentUri= */ true);
+ if (!canAccessUri) {
+ throw new SecurityException(
+ "Provider uid " + callingUid + " cannot access URI " + uri);
+ }
+ break;
+ // android.resource:// URIs are always allowed.
+ case ContentResolver.SCHEME_ANDROID_RESOURCE:
+ break;
+ // file:// and any other schemes are disallowed.
+ case ContentResolver.SCHEME_FILE:
+ default:
+ throw new SecurityException("Disallowed URI " + uri + " in RemoteViews.");
+ }
+ });
+ }
+
+ /**
+ * Increment the counter of widget ids and return the new id.
+ *
+ * Typically called by {@link #allocateAppWidgetId} when a instance of widget is created,
+ * either as a result of being pinned by launcher or added during a restore.
+ *
+ * Note: A widget id is a monotonically increasing integer that uniquely identifies the widget
+ * instance.
+ *
+ * TODO: Revisit this method and determine whether we need to alter the widget id during
+ * the restore since widget id mismatch potentially leads to some issues in the past.
+ */
private int incrementAndGetAppWidgetIdLocked(int userId) {
final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
mNextAppWidgetIds.put(userId, appWidgetId);
return appWidgetId;
}
+ /**
+ * Called by {@link #readProfileStateFromFileLocked} when widgets/providers/hosts are loaded
+ * from disk, which ensures mNextAppWidgetIds is larger than any existing widget id for given
+ * user.
+ */
private void setMinAppWidgetIdLocked(int userId, int minWidgetId) {
final int nextAppWidgetId = peekNextAppWidgetIdLocked(userId);
if (nextAppWidgetId < minWidgetId) {
@@ -2385,10 +2742,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
if (provider.broadcast != null) {
final PendingIntent broadcast = provider.broadcast;
- mSaveStateHandler.post(() -> {
- mAlarmManager.cancel(broadcast);
- broadcast.cancel();
- });
+ Runnable cancelRunnable = () -> {
+ mAlarmManager.cancel(broadcast);
+ broadcast.cancel();
+ };
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mAlarmHandler.post(cancelRunnable);
+ } else {
+ mSaveStateHandler.post(cancelRunnable);
+ }
provider.broadcast = null;
}
}
@@ -2965,6 +3327,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
deleteWidgetsLocked(provider, UserHandle.USER_ALL);
mProviders.remove(provider);
mGeneratedPreviewsApiCounter.remove(provider.id);
+ if (remoteViewsProto()) {
+ clearGeneratedPreviewsAsync(provider);
+ }
// no need to send the DISABLE broadcast, since the receiver is gone anyway
cancelBroadcastsLocked(provider);
@@ -3065,10 +3430,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// invariant and established the PendingIntent safely.
final long period = Math.max(info.updatePeriodMillis, MIN_UPDATE_PERIOD);
final PendingIntent broadcast = provider.broadcast;
- mSaveStateHandler.post(() ->
+
+ Runnable repeatRunnable = () -> {
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + period, period, broadcast)
- );
+ SystemClock.elapsedRealtime() + period, period, broadcast);
+ };
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mAlarmHandler.post(repeatRunnable);
+ } else {
+ mSaveStateHandler.post(repeatRunnable);
+ }
}
}
}
@@ -3543,6 +3914,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
} catch (IOException e) {
Slog.w(TAG, "Failed to read state: " + e);
}
+
+ if (remoteViewsProto()) {
+ try {
+ loadGeneratedPreviewCategoriesLocked(profileId);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to read preview categories: " + e);
+ }
+ }
}
if (version >= 0) {
@@ -4101,19 +4480,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
int version = fromVersion;
- // Update 1: keyguard moved from package "android" to "com.android.keyguard"
+ // Update 1: From version 0 to 1, was used from Android 4 to Android 5. It updated the
+ // location of the keyguard widget database. No modern device will have db version 0.
if (version == 0) {
- HostId oldHostId = new HostId(Process.myUid(),
- KEYGUARD_HOST_ID, OLD_KEYGUARD_HOST_PACKAGE);
-
- Host host = lookupHostLocked(oldHostId);
- if (host != null) {
- final int uid = getUidForPackage(NEW_KEYGUARD_HOST_PACKAGE,
- UserHandle.USER_SYSTEM);
- if (uid >= 0) {
- host.id = new HostId(uid, KEYGUARD_HOST_ID, NEW_KEYGUARD_HOST_PACKAGE);
- }
- }
+ Slog.e(TAG, "Found widget database with version 0, this should not be possible,"
+ + " forcing upgrade to version 1");
version = 1;
}
@@ -4123,24 +4494,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
- private static File getStateFile(int userId) {
- return new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME);
- }
-
private static AtomicFile getSavedStateFile(int userId) {
- File dir = Environment.getUserSystemDirectory(userId);
- File settingsFile = getStateFile(userId);
- if (!settingsFile.exists() && userId == UserHandle.USER_SYSTEM) {
- if (!dir.exists()) {
- dir.mkdirs();
- }
- // Migrate old data
- File oldFile = new File("/data/system/" + STATE_FILENAME);
- // Method doesn't throw an exception on failure. Ignore any errors
- // in moving the file (like non-existence)
- oldFile.renameTo(settingsFile);
- }
- return new AtomicFile(settingsFile);
+ return new AtomicFile(new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME));
}
void onUserStopped(int userId) {
@@ -4312,6 +4667,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
keep.add(providerId);
// Use the new AppWidgetProviderInfo.
provider.setPartialInfoLocked(info);
+ // Clear old previews
+ if (remoteViewsProto()) {
+ clearGeneratedPreviewsAsync(provider);
+ } else {
+ provider.clearGeneratedPreviewsLocked();
+ }
// If it's enabled
final int M = provider.widgets.size();
if (M > 0) {
@@ -4603,6 +4964,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mSecurityPolicy.enforceCallFromPackage(callingPackage);
ensureWidgetCategoryCombinationIsValid(widgetCategory);
+ AndroidFuture<RemoteViews> result = null;
synchronized (mLock) {
ensureGroupStateLoadedLocked(profileId);
final int providerCount = mProviders.size();
@@ -4624,7 +4986,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
final int callingUid = Binder.getCallingUid();
final String providerPackageName = componentName.getPackageName();
final boolean providerIsInCallerProfile =
- mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ mSecurityPolicy.canAccessProvider(
providerPackageName, providerProfileId);
final boolean shouldFilterAppAccess = mPackageManagerInternal.filterAppAccess(
providerPackageName, callingUid, providerProfileId);
@@ -4636,10 +4998,23 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
callingPackage);
if (providerIsInCallerProfile && !shouldFilterAppAccess
&& (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
- return provider.getGeneratedPreviewLocked(widgetCategory);
+ if (remoteViewsProto()) {
+ result = getGeneratedPreviewsAsync(provider, widgetCategory);
+ } else {
+ return provider.getGeneratedPreviewLocked(widgetCategory);
+ }
}
}
}
+
+ if (result != null) {
+ try {
+ return result.get();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get generated previews Future result", e);
+ return null;
+ }
+ }
// Either the provider does not exist or the caller does not have permission to access its
// previews.
return null;
@@ -4669,8 +5044,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
providerComponent + " is not a valid AppWidget provider");
}
if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
- provider.setGeneratedPreviewLocked(widgetCategories, preview);
- scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ if (remoteViewsProto()) {
+ setGeneratedPreviewsAsync(provider, widgetCategories, preview);
+ } else {
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
return true;
}
return false;
@@ -4698,9 +5077,359 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
throw new IllegalArgumentException(
providerComponent + " is not a valid AppWidget provider");
}
- final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
- if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+
+ if (remoteViewsProto()) {
+ removeGeneratedPreviewsAsync(provider, widgetCategories);
+ } else {
+ final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+ if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
+ }
+ }
+
+ /**
+ * Return previews for the specified provider from a background thread. The result of the future
+ * is nullable.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync(
+ @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ AndroidFuture<RemoteViews> result = new AndroidFuture<>();
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ for (int i = 0; i < previews.size(); i++) {
+ if ((widgetCategory & previews.keyAt(i)) != 0) {
+ result.complete(previews.valueAt(i));
+ return;
+ }
+ }
+ result.complete(null);
+ });
+ return result;
+ }
+
+ /**
+ * Set previews for the specified provider on a background thread.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories,
+ @NonNull RemoteViews preview) {
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ previews.put(flag, preview);
+ }
+ }
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ });
+ }
+
+ /**
+ * Remove previews for the specified provider on a background thread.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) {
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ boolean changed = false;
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ changed |= previews.removeReturnOld(flag) != null;
+ }
+ }
+ if (changed) {
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ }
+ });
+ }
+
+ /**
+ * Clear previews for the specified provider on a background thread. Returns true if changed
+ * (i.e. there are previews to clear). If returns true, the caller should schedule a providers
+ * changed notification.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) {
+ mSavePreviewsHandler.post(() -> {
+ saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false);
+ });
+ return provider.info.generatedPreviewCategories != 0;
+ }
+
+ private void checkSavePreviewsThread() {
+ if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("Only modify previews on the background thread");
+ }
+ }
+
+ /**
+ * Load previews from file for the given provider. If there are no previews, returns an empty
+ * SparseArray. Else, returns a SparseArray of the previews mapped by widget category.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) {
+ checkSavePreviewsThread();
+ try {
+ AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+ if (!previewsFile.exists()) {
+ return new SparseArray<>();
+ }
+ ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+ SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input);
+ SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>();
+ for (int i = 0; i < entries.size(); i++) {
+ int widgetCategories = entries.keyAt(i);
+ RemoteViews preview = entries.valueAt(i);
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ singleCategoryKeyedEntries.put(flag, preview);
+ }
+ }
+ }
+ return singleCategoryKeyedEntries;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to load generated previews for " + provider, e);
+ return new SparseArray<>();
+ }
+ }
+
+ /**
+ * This is called when loading profile/group state to populate
+ * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved.
+ *
+ * This is the only time previews are read while not on mSavePreviewsHandler. It happens once
+ * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync
+ * happen for that profile.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @GuardedBy("mLock")
+ private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException {
+ for (Provider provider : mProviders) {
+ if (provider.id.getProfile().getIdentifier() != profileId) {
+ continue;
+ }
+ AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+ if (!previewsFile.exists()) {
+ continue;
+ }
+ ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+ provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+ input);
+ if (DEBUG) {
+ Slog.i(TAG, TextUtils.formatSimple(
+ "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
+ provider, provider.info.generatedPreviewCategories));
+ }
+ }
+ }
+
+ /**
+ * Save the given previews into storage.
+ *
+ * @param provider Provider for which to save previews
+ * @param previews Previews to save. If null or empty, clears any saved previews for this
+ * provider.
+ * @param notify If true, then this function will notify hosts of updated provider info.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void saveGeneratedPreviews(@NonNull Provider provider,
+ @Nullable SparseArray<RemoteViews> previews, boolean notify) {
+ checkSavePreviewsThread();
+ AtomicFile file = null;
+ FileOutputStream stream = null;
+ try {
+ file = getWidgetPreviewsFile(provider);
+ if (previews == null || previews.size() == 0) {
+ if (file.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Deleting widget preview file " + file);
+ }
+ file.delete();
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Writing widget preview file " + file);
+ }
+ ProtoOutputStream out = new ProtoOutputStream();
+ writePreviewsToProto(out, previews);
+ stream = file.startWrite();
+ stream.write(out.getBytes());
+ file.finishWrite(stream);
+ }
+
+ synchronized (mLock) {
+ provider.updateGeneratedPreviewCategoriesLocked(previews);
+ if (notify) {
+ scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId());
+ }
+ }
+ } catch (IOException e) {
+ if (file != null && stream != null) {
+ file.failWrite(stream);
+ }
+ Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName);
+ }
+ }
+
+
+ /**
+ * Write the given previews as a GeneratedPreviewsProto to the output stream.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void writePreviewsToProto(@NonNull ProtoOutputStream out,
+ @NonNull SparseArray<RemoteViews> generatedPreviews) {
+ // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates.
+ SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>();
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ int widgetCategory = generatedPreviews.keyAt(i);
+ RemoteViews views = generatedPreviews.valueAt(i);
+ if (!previewsToWrite.contains(views.hashCode())) {
+ previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views));
+ } else {
+ Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode());
+ previewsToWrite.put(views.hashCode(),
+ Pair.create(entry.first | widgetCategory, views));
+ }
+ }
+
+ for (int i = 0; i < previewsToWrite.size(); i++) {
+ final long token = out.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i);
+ out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first);
+ final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS);
+ entry.second.writePreviewToProto(mContext, out);
+ out.end(viewsToken);
+ out.end(token);
+ }
+ }
+
+ /**
+ * Read a GeneratedPreviewsProto message from the input stream.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input)
+ throws IOException {
+ SparseArray<RemoteViews> entries = new SparseArray<>();
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.PREVIEWS:
+ final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+ /* skipViews= */ false);
+ entries.put(entry.first, entry.second);
+ input.end(token);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return entries;
+ }
+
+ /**
+ * Read the widget categories from GeneratedPreviewsProto and return an int representing the
+ * combined widget categories of all the previews.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @AppWidgetProviderInfo.CategoryFlags
+ private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input)
+ throws IOException {
+ int widgetCategories = 0;
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.PREVIEWS:
+ final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+ /* skipViews= */ true);
+ widgetCategories |= entry.first;
+ input.end(token);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
}
+ return widgetCategories;
+ }
+
+ /**
+ * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a
+ * pair of widget category and corresponding RemoteViews. If skipViews is true, this function
+ * will only read widget categories and the returned RemoteViews will be null.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input,
+ boolean skipViews) throws IOException {
+ int widgetCategories = 0;
+ RemoteViews views = null;
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.Preview.VIEWS:
+ if (skipViews) {
+ // ProtoInputStream will skip over the nested message when nextField() is
+ // called.
+ continue;
+ }
+ final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS);
+ try {
+ views = RemoteViews.createPreviewFromProto(mContext, input);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to deserialize RemoteViews", e);
+ }
+ input.end(token);
+ break;
+ case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES:
+ widgetCategories = input.readInt(
+ GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return Pair.create(widgetCategories, views);
+ }
+
+ /**
+ * Returns the file in which all generated previews for this provider are stored. This will be
+ * a path of the form:
+ * {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb}
+ *
+ * This function will not create the file if it does not already exist.
+ */
+ @NonNull
+ private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException {
+ int userId = provider.getUserId();
+ File previewsDirectory = getWidgetPreviewsDirectory(userId);
+ File providerPreviews = Environment.buildPath(previewsDirectory,
+ TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(),
+ provider.id.componentName.getClassName(), provider.id.uid));
+ return new AtomicFile(providerPreviews);
+ }
+
+ /**
+ * Returns the widget previews directory for the given user, creating it if it does not exist.
+ * This will be a path of the form:
+ * {@literal /data/system_ce/<userId>/appwidget/previews}
+ */
+ @NonNull
+ private static File getWidgetPreviewsDirectory(int userId) throws IOException {
+ File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId);
+ File previewsDirectory = Environment.buildPath(dataSystemCeDirectory,
+ APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME);
+ if (!previewsDirectory.exists()) {
+ if (!previewsDirectory.mkdirs()) {
+ throw new IOException("Unable to create widget preview directory "
+ + previewsDirectory.getPath());
+ }
+ }
+ return previewsDirectory;
}
private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
@@ -4950,12 +5679,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
return true;
}
final int userId = UserHandle.getUserId(uid);
- if ((widget.host.getUserId() == userId || (widget.provider != null
- && widget.provider.getUserId() == userId))
- && mContext.checkCallingPermission(android.Manifest.permission.BIND_APPWIDGET)
- == PackageManager.PERMISSION_GRANTED) {
- // Apps that run in the same user as either the host or the provider and
- // have the bind widget permission have access to the widget.
+ if ((widget.host.getUserId() == userId
+ || (widget.provider != null && widget.provider.getUserId() == userId)
+ || hasCallerInteractAcrossUsersPermission())
+ && callerHasPermission(android.Manifest.permission.BIND_APPWIDGET)) {
+ // Access to the widget requires the app to:
+ // - Run in the same user as the host or provider, or have permission to interact
+ // across users
+ // - Have bind widget permission
return true;
}
if (DEBUG) {
@@ -4972,8 +5703,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
return getProfileParent(profileId) == parentId;
}
- public boolean isProviderInCallerOrInProfileAndWhitelListed(String packageName,
- int profileId) {
+ /**
+ * The provider is accessible by the caller if any of the following is true:
+ * - The provider belongs to the caller
+ * - The provider belongs to a profile of the caller and is allowlisted
+ */
+ public boolean canAccessProvider(String packageName, int profileId) {
final int callerId = UserHandle.getCallingUserId();
if (profileId == callerId) {
return true;
@@ -5045,6 +5780,20 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
return true;
}
+
+ /** Returns true if the caller has permission to interact across users. */
+ public boolean hasCallerInteractAcrossUsersPermission() {
+ if (!securityPolicyInteractAcrossUsers()) {
+ return false;
+ }
+
+ return callerHasPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ || callerHasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
+ private boolean callerHasPermission(@NonNull @PermissionName String permission) {
+ return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ }
}
static final class Provider {
@@ -5113,11 +5862,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
}
if (newInfo != null) {
+ newInfo.generatedPreviewCategories = info.generatedPreviewCategories;
info = newInfo;
if (DEBUG) {
Objects.requireNonNull(info);
}
- updateGeneratedPreviewCategoriesLocked();
}
}
mInfoParsed = true;
@@ -5174,7 +5923,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
generatedPreviews.put(flag, preview);
}
}
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
}
@GuardedBy("this.mLock")
@@ -5186,7 +5935,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
if (changed) {
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
}
return changed;
}
@@ -5195,17 +5944,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
public boolean clearGeneratedPreviewsLocked() {
if (generatedPreviews.size() > 0) {
generatedPreviews.clear();
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
return true;
}
return false;
}
-
@GuardedBy("this.mLock")
- private void updateGeneratedPreviewCategoriesLocked() {
+ private void updateGeneratedPreviewCategoriesLocked(
+ @Nullable SparseArray<RemoteViews> previews) {
info.generatedPreviewCategories = 0;
- for (int i = 0; i < generatedPreviews.size(); i++) {
- info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+ if (previews != null) {
+ for (int i = 0; i < previews.size(); i++) {
+ info.generatedPreviewCategories |= previews.keyAt(i);
+ }
}
}
diff --git a/services/art-profile b/services/art-profile
index 6fa4c88cb1f6..ce1e2c6f1397 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -5657,7 +5657,7 @@ Lcom/android/server/utils/WatchedSparseSetArray;
Lcom/android/server/utils/Watcher;
Lcom/android/server/vibrator/VibratorController$NativeWrapper;
Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;
-Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;
+Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;
Lcom/android/server/vibrator/VibratorManagerService;
Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener;
Lcom/android/server/vr/VrManagerService;
diff --git a/services/art-wear-profile b/services/art-wear-profile
index 42c4a01c5c2f..f080715643ca 100644
--- a/services/art-wear-profile
+++ b/services/art-wear-profile
@@ -1330,7 +1330,7 @@ Lcom/android/server/utils/WatchedSparseSetArray;
Lcom/android/server/utils/Watcher;
Lcom/android/server/vibrator/VibratorController$NativeWrapper;
Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;
-Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;
+Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;
Lcom/android/server/vibrator/VibratorManagerService;
Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener;
Lcom/android/server/vr/VrManagerService;
@@ -24948,7 +24948,7 @@ PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;-><init>()V
PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->cancelSynced()V
PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getCapabilities()J
PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getVibratorIds()[I
-PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)V
+PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)V
PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;-><init>(Lcom/android/server/vibrator/VibratorManagerService;)V
PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;->onComplete(IJ)V
PLcom/android/server/vibrator/VibratorManagerService$VibrationRecords;-><init>(II)V
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index ba8448548365..9c83757f4b0f 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -9,6 +9,16 @@ flag {
}
flag {
+ name: "improve_fill_dialog_aconfig"
+ namespace: "autofill"
+ description: "Improvements for Fill Dialog. Guard DeviceConfig rollout "
+ bug: "382493181"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fill_fields_from_current_session_only"
namespace: "autofill"
description: "Only fill autofill fields that are part of the current session."
@@ -23,6 +33,16 @@ flag {
}
flag {
+ name: "relayout_fix"
+ namespace: "autofill"
+ description: "Fixing relayout issue. Guarding enabling device config flags"
+ bug: "381226145"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "include_invisible_view_group_in_assist_structure"
namespace: "autofill"
description: "Mitigation for autofill providers miscalculating view visibility"
@@ -44,8 +64,25 @@ flag {
}
flag {
+ name: "test_flag"
+ namespace: "autofill"
+ description: "Test flag "
+ bug: "377868687"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "add_session_id_to_client_state"
namespace: "autofill"
description: "Include the session id into the FillEventHistory events as part of ClientState"
bug: "333927465"
}
+
+flag {
+ name: "highlight_autofill_single_field"
+ namespace: "autofill"
+ description: "Highlight single field after autofill selection"
+ bug: "41496744"
+}
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1dc3b73d2bd3..b78d103f287c 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -2,6 +2,21 @@ package: "android.service.autofill"
container: "system"
flag {
+ name: "autofill_session_destroyed"
+ namespace: "autofill"
+ description: "Guards against new metrics definitions introduced in W"
+ bug: "342676602"
+}
+
+flag {
+ name: "autofill_w_metrics"
+ namespace: "autofill"
+ description: "Guards against new metrics definitions introduced in W"
+ bug: "342676602"
+ is_exported: true
+}
+
+flag {
name: "autofill_credman_integration"
namespace: "autofill"
description: "Guards Autofill Framework against Autofill-Credman integration"
@@ -22,3 +37,26 @@ flag {
description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
bug: "320730001"
}
+
+flag {
+ name: "fill_dialog_improvements_impl"
+ is_exported: true
+ namespace: "autofill"
+ description: "Improvements for Fill Dialog for non-api changes"
+ bug: "336223371"
+}
+
+flag {
+ name: "fill_dialog_improvements"
+ is_exported: true
+ namespace: "autofill"
+ description: "Improvements for Fill Dialog, including deprecation of pre-trigger API's"
+ bug: "336223371"
+}
+
+flag {
+ name: "add_last_focused_id_to_fill_event_history"
+ namespace: "autofill"
+ description: "Adds focused id to each event that's part of the fill event history"
+ bug: "334141398"
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
index 219b788448e8..5e7e557d7041 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -354,6 +354,13 @@ final class AutofillInlineSuggestionsRequestSession {
}
}
+ private void handleOnInputMethodStartInputView() {
+ synchronized (mLock) {
+ mUiCallback.onInputMethodStartInputView();
+ handleOnReceiveImeStatusUpdated(true, true);
+ }
+ }
+
/**
* Handles the IME session status received from the IME.
*
@@ -437,8 +444,8 @@ final class AutofillInlineSuggestionsRequestSession {
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(
- AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
- session, true, true));
+ AutofillInlineSuggestionsRequestSession::handleOnInputMethodStartInputView,
+ session));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 259ea148f163..cba8c66cd5e3 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -2139,6 +2139,32 @@ public final class AutofillManagerService
}
@Override
+ public void notifyImeAnimationStart(int sessionId, long startTimeMs, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.notifyImeAnimationStart(sessionId, startTimeMs, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "notifyImeAnimationStart(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
+ public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.notifyImeAnimationEnd(sessionId, endTimeMs, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "notifyImeAnimationEnd(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index b52c65054e51..0fa43ac7091b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -61,6 +61,7 @@ import android.service.autofill.FillEventHistory;
import android.service.autofill.FillEventHistory.Event;
import android.service.autofill.FillEventHistory.Event.NoSaveReason;
import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
import android.service.autofill.IAutoFillService;
import android.service.autofill.InlineSuggestionRenderService;
import android.service.autofill.SaveInfo;
@@ -100,6 +101,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
+
/**
* Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
* app's {@link IAutoFillService} implementation.
@@ -748,6 +750,22 @@ final class AutofillManagerServiceImpl
@GuardedBy("mLock")
void removeSessionLocked(int sessionId) {
mSessions.remove(sessionId);
+ if (Flags.autofillSessionDestroyed()) {
+ if (sVerbose) {
+ Slog.v(
+ TAG,
+ "removeSessionLocked(): removed " + sessionId);
+ }
+ RemoteFillService remoteService =
+ new RemoteFillService(
+ getContext(),
+ mInfo.getServiceInfo().getComponentName(),
+ mUserId,
+ /* callbacks= */ null,
+ mMaster.isInstantServiceAllowed(),
+ /* credentialAutofillService= */ null);
+ remoteService.onSessionDestroyed(null);
+ }
}
/**
@@ -805,6 +823,36 @@ final class AutofillManagerServiceImpl
}
@GuardedBy("mLock")
+ public void notifyImeAnimationStart(int sessionId, long startTimeMs, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "notifyImeAnimationStart(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.notifyImeAnimationStart(startTimeMs);
+ }
+
+ @GuardedBy("mLock")
+ public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "notifyImeAnimationEnd(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.notifyImeAnimationEnd(endTimeMs);
+ }
+
+ @GuardedBy("mLock")
@Override // from PerUserSystemService
protected void handlePackageUpdateLocked(@NonNull String packageName) {
final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo();
@@ -900,13 +948,13 @@ final class AutofillManagerServiceImpl
* Updates the last fill selection when an authentication was selected.
*/
void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
- int uiType) {
+ int uiType, @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
null, null, null, null, null, null,
- NO_SAVE_UI_REASON_NONE, uiType));
+ NO_SAVE_UI_REASON_NONE, uiType, focusedId));
}
}
}
@@ -915,13 +963,13 @@ final class AutofillManagerServiceImpl
* Updates the last fill selection when an dataset authentication was selected.
*/
void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
- @Nullable Bundle clientState, int uiType) {
+ @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
clientState, null, null, null, null, null, null, null, null,
- NO_SAVE_UI_REASON_NONE, uiType));
+ NO_SAVE_UI_REASON_NONE, uiType, focusedId));
}
}
}
@@ -933,7 +981,7 @@ final class AutofillManagerServiceImpl
synchronized (mLock) {
if (isValidEventLocked("logSaveShown()", sessionId)) {
mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
- null, null, null, null, null, null, null));
+ null, null, null, null, null, null, null, /* focusedId= */ null));
}
}
}
@@ -942,13 +990,13 @@ final class AutofillManagerServiceImpl
* Updates the last fill response when a dataset was selected.
*/
void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
- @Nullable Bundle clientState, int uiType) {
+ @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
- uiType));
+ uiType, focusedId));
}
}
}
@@ -956,13 +1004,14 @@ final class AutofillManagerServiceImpl
/**
* Updates the last fill response when a dataset is shown.
*/
- void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType) {
+ void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType,
+ @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetShown", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
- uiType));
+ uiType, focusedId));
}
}
}
@@ -970,7 +1019,8 @@ final class AutofillManagerServiceImpl
/**
* Updates the last fill response when a view was entered.
*/
- void logViewEntered(int sessionId, @Nullable Bundle clientState) {
+ void logViewEntered(int sessionId, @Nullable Bundle clientState,
+ @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (!isValidEventLocked("logViewEntered", sessionId)) {
return;
@@ -988,7 +1038,7 @@ final class AutofillManagerServiceImpl
mEventHistory.addEvent(
new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
- null, null, null, null, null, null, null));
+ null, null, null, null, null, null, null, focusedId));
}
}
@@ -1001,7 +1051,8 @@ final class AutofillManagerServiceImpl
}
mAugmentedAutofillEventHistory.addEvent(
new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
- clientState, null, null, null, null, null, null, null, null));
+ clientState, null, null, null, null, null, null, null, null,
+ /* focusedId= */ null));
}
}
@@ -1014,7 +1065,7 @@ final class AutofillManagerServiceImpl
}
mAugmentedAutofillEventHistory.addEvent(
new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
- null, null, null, null, null, null));
+ null, null, null, null, null, null, /* focusedId= */ null));
}
}
@@ -1029,7 +1080,7 @@ final class AutofillManagerServiceImpl
mAugmentedAutofillEventHistory.addEvent(
new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
- UI_TYPE_INLINE));
+ UI_TYPE_INLINE, /* focusedId= */ null));
}
}
@@ -1113,7 +1164,8 @@ final class AutofillManagerServiceImpl
clientState, selectedDatasets, ignoredDatasets,
changedFieldIds, changedDatasetIds,
manuallyFilledFieldIds, manuallyFilledDatasetIds,
- detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason));
+ detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason,
+ /* focusedId= */ null));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index cd2a535aa2c5..4f632c9e28a1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -28,8 +28,11 @@ import android.app.ActivityManager;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.metrics.LogMaker;
import android.os.UserHandle;
@@ -97,11 +100,12 @@ public final class Helper {
@UserIdInt int userId, @NonNull RemoteViews rView) {
final AtomicBoolean permissionsOk = new AtomicBoolean(true);
- rView.visitUris(uri -> {
- int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri);
- boolean allowed = uriOwnerId == userId;
- permissionsOk.set(allowed & permissionsOk.get());
- });
+ rView.visitUris(
+ uri -> {
+ int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri, userId);
+ boolean allowed = uriOwnerId == userId;
+ permissionsOk.set(allowed & permissionsOk.get());
+ });
return permissionsOk.get();
}
@@ -150,6 +154,47 @@ public final class Helper {
return (ok ? rView : null);
}
+ /**
+ * Checks the URI permissions of the icon in the slice, to see if the current userId is able to
+ * access it.
+ *
+ * <p>Returns null if slice contains user inaccessible icons
+ *
+ * <p>TODO: instead of returning a null Slice when the current userId cannot access an icon,
+ * return a reconstructed Slice without the icons. This is currently non-trivial since there are
+ * no public methods to generically add SliceItems to Slices
+ */
+ public static @Nullable Slice sanitizeSlice(Slice slice) {
+ if (slice == null) {
+ return null;
+ }
+
+ int userId = ActivityManager.getCurrentUser();
+
+ // Recontruct the Slice, filtering out bad icons
+ for (SliceItem sliceItem : slice.getItems()) {
+ if (!sliceItem.getFormat().equals(SliceItem.FORMAT_IMAGE)) {
+ // Not an image slice
+ continue;
+ }
+
+ Icon icon = sliceItem.getIcon();
+ if (icon.getType() != Icon.TYPE_URI
+ && icon.getType() != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // No URIs to sanitize
+ continue;
+ }
+
+ int iconUriId = android.content.ContentProvider.getUserIdFromUri(icon.getUri(), userId);
+
+ if (iconUriId != userId) {
+ Slog.w(TAG, "sanitizeSlice() user: " + userId + " cannot access icons in Slice");
+ return null;
+ }
+ }
+
+ return slice;
+ }
@Nullable
static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) {
@@ -308,7 +353,10 @@ public final class Helper {
private static void addAutofillableIds(@NonNull ViewNode node,
@NonNull ArrayList<AutofillId> ids, boolean autofillableOnly) {
if (!autofillableOnly || node.getAutofillType() != View.AUTOFILL_TYPE_NONE) {
- ids.add(node.getAutofillId());
+ AutofillId id = node.getAutofillId();
+ if (id != null) {
+ ids.add(id);
+ }
}
final int size = node.getChildCount();
for (int i = 0; i < size; i++) {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 07f5dcc3cb0a..f1e888400d32 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -34,6 +34,7 @@ import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.ConvertCredentialRequest;
import android.service.autofill.ConvertCredentialResponse;
+import android.service.autofill.FillEventHistory;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
@@ -497,6 +498,14 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}));
}
+ public void onSessionDestroyed(@Nullable FillEventHistory history) {
+ boolean success = run(service -> {
+ service.onSessionDestroyed(history);
+ });
+
+ if (sVerbose) Slog.v(TAG, "called onSessionDestroyed(): " + success);
+ }
+
void onSavedPasswordCountRequest(IResultReceiver receiver) {
run(service -> service.onSavedPasswordCountRequest(receiver));
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 2fa0e0d0d946..9c6e4741730a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -39,6 +39,8 @@ import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.service.autofill.Flags.highlightAutofillSingleField;
+import static android.service.autofill.Flags.improveFillDialogAconfig;
import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
@@ -104,7 +106,6 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
import android.app.ActivityTaskManager;
import android.app.IAssistDataReceiver;
import android.app.PendingIntent;
@@ -143,7 +144,6 @@ import android.os.TransactionTooLargeException;
import android.service.assist.classification.FieldClassificationRequest;
import android.service.assist.classification.FieldClassificationResponse;
import android.service.autofill.AutofillFieldClassificationService.Scores;
-import android.service.autofill.AutofillService;
import android.service.autofill.CompositeUserData;
import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.Dataset;
@@ -222,18 +222,21 @@ import java.util.function.Function;
/**
* A session for a given activity.
*
- * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
- * of the current {@link ViewState} to display the appropriate UI.
+ * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track of
+ * the current {@link ViewState} to display the appropriate UI.
*
- * <p>Although the autofill requests and callbacks are stateless from the service's point of
- * view, we need to keep state in the framework side for cases such as authentication. For
- * example, when service return a {@link FillResponse} that contains all the fields needed
- * to fill the activity but it requires authentication first, that response need to be held
- * until the user authenticates or it times out.
+ * <p>Although the autofill requests and callbacks are stateless from the service's point of view,
+ * we need to keep state in the framework side for cases such as authentication. For example, when
+ * service return a {@link FillResponse} that contains all the fields needed to fill the activity
+ * but it requires authentication first, that response need to be held until the user authenticates
+ * or it times out.
*/
-final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
- AutoFillUI.AutoFillUiCallback, ValueFinder,
- RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
+final class Session
+ implements RemoteFillService.FillServiceCallbacks,
+ ViewState.Listener,
+ AutoFillUI.AutoFillUiCallback,
+ ValueFinder,
+ RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
private static final String TAG = "AutofillSession";
// This should never be true in production. This is only for local debugging.
@@ -249,6 +252,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private static final int DEFAULT__FILL_REQUEST_ID_SNAPSHOT = -2;
private static final int DEFAULT__FIELD_CLASSIFICATION_REQUEST_ID_SNAPSHOT = -2;
+ private static final long DEFAULT_UNASSIGNED_TIME = -1;
+
static final String SESSION_ID_KEY = "autofill_session_id";
static final String REQUEST_ID_KEY = "autofill_request_id";
@@ -257,6 +262,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private final AutofillManagerServiceImpl mService;
private final Handler mHandler;
private final AutoFillUI mUi;
+
/**
* Context associated with the session, it has the same {@link Context#getDisplayId() displayId}
* of the activity being autofilled.
@@ -286,14 +292,36 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/** Session is destroyed and removed from the manager service. */
public static final int STATE_REMOVED = 3;
- @IntDef(prefix = { "STATE_" }, value = {
- STATE_UNKNOWN,
- STATE_ACTIVE,
- STATE_FINISHED,
- STATE_REMOVED
+ @IntDef(
+ prefix = {"STATE_"},
+ value = {STATE_UNKNOWN, STATE_ACTIVE, STATE_FINISHED, STATE_REMOVED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SessionState {}
+
+ /**
+ * Indicates fill dialog will not be shown.
+ */
+ private static final int SHOW_FILL_DIALOG_NO = 0;
+
+ /**
+ * Indicates fill dialog is shown.
+ */
+ private static final int SHOW_FILL_DIALOG_YES = 1;
+
+ /**
+ * Indicates fill dialog can be shown, but we need to wait.
+ * For eg, if the IME animation is happening, we need for it to complete before fill dialog can
+ * be shown.
+ */
+ private static final int SHOW_FILL_DIALOG_WAIT = 2;
+
+ @IntDef(prefix = { "SHOW_FILL_DIALOG_" }, value = {
+ SHOW_FILL_DIALOG_NO,
+ SHOW_FILL_DIALOG_YES,
+ SHOW_FILL_DIALOG_WAIT
})
@Retention(RetentionPolicy.SOURCE)
- @interface SessionState{}
+ private @interface ShowFillDialogState{}
@GuardedBy("mLock")
private final SessionFlags mSessionFlags;
@@ -318,7 +346,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public final int mFlags;
@GuardedBy("mLock")
- @NonNull private IBinder mActivityToken;
+ @NonNull
+ private IBinder mActivityToken;
/** The app activity that's being autofilled */
@NonNull private final ComponentName mComponentName;
@@ -341,11 +370,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* autofill.
*/
@GuardedBy("mLock")
- @Nullable private Pair<Integer, InlineSuggestionsRequest> mLastInlineSuggestionsRequest;
+ @Nullable
+ private Pair<Integer, InlineSuggestionsRequest> mLastInlineSuggestionsRequest;
- /**
- * Id of the View currently being displayed.
- */
+ /** Id of the View currently being displayed. */
@GuardedBy("mLock")
private @Nullable AutofillId mCurrentViewId;
@@ -363,8 +391,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*
* <p>Only {@code null} when the session is for augmented autofill only.
*/
- @Nullable
- private final RemoteFillService mRemoteFillService;
+ @Nullable private final RemoteFillService mRemoteFillService;
/**
* With the credman integration, Autofill Framework handles two types of autofill flows -
@@ -379,8 +406,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* service the session was initially created with, the secondary provider handler will contain
* the remaining autofill service.
*/
- @Nullable
- private final SecondaryProviderHandler mSecondaryProviderHandler;
+ @Nullable private final SecondaryProviderHandler mSecondaryProviderHandler;
@GuardedBy("mLock")
private SparseArray<FillResponse> mResponses;
@@ -395,9 +421,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private ArrayList<FillContext> mContexts;
- /**
- * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
- */
+ /** Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. */
private boolean mHasCallback;
/** Whether the session has credential manager provider as the primary provider. */
@@ -426,32 +450,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private PendingUi mPendingSaveUi;
- /**
- * List of dataset ids selected by the user.
- */
+ /** List of dataset ids selected by the user. */
@GuardedBy("mLock")
private ArrayList<String> mSelectedDatasetIds;
- /**
- * When the session started (using elapsed time since boot).
- */
+ /** When the session started (using elapsed time since boot). */
private final long mStartTime;
- /**
- * Count of FillRequests in the session.
- */
+ /** Count of FillRequests in the session. */
private int mRequestCount;
/**
- * Starting timestamp of latency logger.
- * This is set when Session created or when the view is reset.
+ * Starting timestamp of latency logger. This is set when Session created or when the view is
+ * reset.
*/
@GuardedBy("mLock")
private long mLatencyBaseTime;
- /**
- * When the UI was shown for the first time (using elapsed time since boot).
- */
+ /** When the UI was shown for the first time (using elapsed time since boot). */
@GuardedBy("mLock")
private long mUiShownTime;
@@ -475,15 +491,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private final LocalLog mWtfHistory;
- /**
- * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
- */
+ /** Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. */
@GuardedBy("mLock")
private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1);
- /**
- * Destroys the augmented Autofill UI.
- */
+ /** Destroys the augmented Autofill UI. */
// TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the
// main reason being the cases where user tap HOME.
// Right now it's completely destroying the UI, but we need to decide whether / how to
@@ -494,13 +506,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Nullable
private Runnable mAugmentedAutofillDestroyer;
- /**
- * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics.
- */
+ /** List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. */
@GuardedBy("mLock")
private ArrayList<LogMaker> mAugmentedRequestsLogs;
-
/**
* List of autofill ids of autofillable fields present in the AssistStructure that can be used
* to trigger new augmented autofill requests (because the "standard" service was not interested
@@ -509,23 +518,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private ArrayList<AutofillId> mAugmentedAutofillableIds;
- @NonNull
- final AutofillInlineSessionController mInlineSessionController;
+ @NonNull final AutofillInlineSessionController mInlineSessionController;
- /**
- * Receiver of assist data from the app's {@link Activity}.
- */
+ /** Receiver of assist data from the app's {@link Activity}. */
private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl();
- /**
- * Receiver of assist data for pcc purpose
- */
+ /** Receiver of assist data for pcc purpose */
private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
private final ClassificationState mClassificationState = new ClassificationState();
- @Nullable
- private final ComponentName mCredentialAutofillService;
+ @Nullable private final ComponentName mCredentialAutofillService;
// TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
// new one per Session.
@@ -547,7 +550,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received");
synchronized (mLock) {
int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
- FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class);
+ FillResponse response =
+ intent.getParcelableExtra(
+ EXTRA_FILL_RESPONSE,
+ android.service.autofill.FillResponse.class);
mFillRequestEventLogger.maybeSetRequestTriggerReason(
TRIGGER_REASON_RETRIGGER);
mAssistReceiver.processDelayedFillLocked(requestId, response);
@@ -575,28 +581,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private SessionCommittedEventLogger mSessionCommittedEventLogger;
- /**
- * Fill dialog request would likely be sent slightly later.
- */
+ /** Fill dialog request would likely be sent slightly later. */
@NonNull
@GuardedBy("mLock")
private boolean mPreviouslyFillDialogPotentiallyStarted;
/**
- * Keeps track of if the user entered view, this is used to
- * distinguish Fill Request that did not have user interaction
- * with ones that did.
+ * Keeps track of if the user entered view, this is used to distinguish Fill Request that did
+ * not have user interaction with ones that did.
*
- * This is set to true when entering view - after FillDialog FillRequest
- * or on plain user tap.
+ * <p>This is set to true when entering view - after FillDialog FillRequest or on plain user
+ * tap.
*/
@NonNull
@GuardedBy("mLock")
private boolean mLogViewEntered;
/**
- * Keeps the fill dialog trigger ids of the last response. This invalidates
- * the trigger ids of the previous response.
+ * Keeps the fill dialog trigger ids of the last response. This invalidates the trigger ids of
+ * the previous response.
*/
@Nullable
@GuardedBy("mLock")
@@ -604,6 +607,47 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private boolean mIgnoreViewStateResetToEmpty;
+ /**
+ * Whether improveFillDialog feature is enabled or not.
+ * Configured via device config flag and aconfig flag.
+ */
+ private final boolean mImproveFillDialogEnabled;
+
+ /**
+ * Timeout, after which, fill dialog won't be shown.
+ * Configured via device config flag.
+ */
+ private final long mFillDialogTimeoutMs;
+
+ /**
+ * Time to wait after ime Animation ends before showing up fill dialog.
+ * Configured via device config flag.
+ */
+ private final long mFillDialogMinWaitAfterImeAnimationMs;
+
+ /**
+ * Indicates the need to wait for ime animation to end before showing up fill dialog.
+ * This is set when we receive notification of ime animation start.
+ * Focussing on one input field from another wouldn't cause ime to re-animate. So this will let
+ * us know whether we need to wait for ime animation finish notification.
+ */
+ private boolean mWaitForImeAnimation;
+
+ /**
+ * A runnable set to run when there is a need to wait for ime animation to end before showing
+ * up fill dialog. This is set only if the fill response has been received, and the response
+ * is eligible for showing up fill dialog, but the ime animation hasn't completed. This allows
+ * for this runnable to be scheduled/run when the ime animation ends, in order to show fill
+ * dialog.
+ */
+ private Runnable mFillDialogRunnable;
+
+ private long mImeAnimationFinishTimeMs = DEFAULT_UNASSIGNED_TIME;
+
+ private long mImeAnimationStartTimeMs = DEFAULT_UNASSIGNED_TIME;
+
+ private long mLastInputStartTime = DEFAULT_UNASSIGNED_TIME;
+
/*
* Id of the previous view that was entered. Once set, it would only be replaced by non-null
* view ids.
@@ -662,9 +706,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
- /**
- * Collection of flags/booleans that helps determine Session behaviors.
- */
+ /** Collection of flags/booleans that helps determine Session behaviors. */
private final class SessionFlags {
/** Whether autofill is disabled by the service */
private boolean mAutofillDisabled;
@@ -695,31 +737,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
@GuardedBy("mLock")
private boolean mWaitForInlineRequest;
+
@GuardedBy("mLock")
private InlineSuggestionsRequest mPendingInlineSuggestionsRequest;
+
@GuardedBy("mLock")
private FillRequest mPendingFillRequest;
+
@GuardedBy("mLock")
private FillRequest mLastFillRequest;
- @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState,
- boolean isInlineRequest) {
+ @Nullable
+ Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(
+ ViewState viewState, boolean isInlineRequest) {
mPendingFillRequest = null;
mWaitForInlineRequest = isInlineRequest;
mPendingInlineSuggestionsRequest = null;
if (isInlineRequest) {
WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference =
- new WeakReference<AssistDataReceiverImpl>(this);
+ new WeakReference<AssistDataReceiverImpl>(this);
WeakReference<ViewState> viewStateWeakReference =
- new WeakReference<ViewState>(viewState);
- return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference,
- viewStateWeakReference);
+ new WeakReference<ViewState>(viewState);
+ return new InlineSuggestionRequestConsumer(
+ assistDataReceiverWeakReference, viewStateWeakReference);
}
return null;
}
- void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
- ViewState viewState) {
+ void handleInlineSuggestionRequest(
+ InlineSuggestionsRequest inlineSuggestionsRequest, ViewState viewState) {
if (sVerbose) {
Slog.v(TAG, "handleInlineSuggestionRequest(): inline suggestion request received");
}
@@ -738,8 +784,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
void maybeRequestFillLocked() {
if (mPendingFillRequest == null) {
if (sVerbose) {
- Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request "
- + "due to empty pending fill request");
+ Slog.v(
+ TAG,
+ "maybeRequestFillLocked(): cancelling calling fill request "
+ + "due to empty pending fill request");
}
return;
}
@@ -748,27 +796,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mWaitForInlineRequest) {
if (mPendingInlineSuggestionsRequest == null) {
if (sVerbose) {
- Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request "
- + "due to waiting for inline request and pending inline request is "
- + "currently empty");
+ Slog.v(
+ TAG,
+ "maybeRequestFillLocked(): cancelling calling fill request due to"
+ + " waiting for inline request and pending inline request is"
+ + " currently empty");
}
return;
}
if (sVerbose) {
- Slog.v(TAG, "maybeRequestFillLocked(): adding inline request to pending "
- + "fill request");
+ Slog.v(
+ TAG,
+ "maybeRequestFillLocked(): adding inline request to pending "
+ + "fill request");
}
- mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
- mPendingFillRequest.getFillContexts(),
- mPendingFillRequest.getHints(),
- mPendingFillRequest.getClientState(),
- mPendingFillRequest.getFlags(),
- mPendingInlineSuggestionsRequest,
- mPendingFillRequest.getDelayedFillIntentSender());
+ mPendingFillRequest =
+ new FillRequest(
+ mPendingFillRequest.getId(),
+ mPendingFillRequest.getFillContexts(),
+ mPendingFillRequest.getHints(),
+ mPendingFillRequest.getClientState(),
+ mPendingFillRequest.getFlags(),
+ mPendingInlineSuggestionsRequest,
+ mPendingFillRequest.getDelayedFillIntentSender());
} else {
if (sVerbose) {
- Slog.v(TAG, "maybeRequestFillLocked(): not adding inline request to pending "
- + "fill request");
+ Slog.v(
+ TAG,
+ "maybeRequestFillLocked(): not adding inline request to pending "
+ + "fill request");
}
}
@@ -780,19 +836,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
&& mSecondaryProviderHandler != null) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
if (!mIsPrimaryCredential) {
- mPendingFillRequest = addCredentialManagerDataToClientState(
- mPendingFillRequest,
- mPendingInlineSuggestionsRequest, id);
+ mPendingFillRequest =
+ addCredentialManagerDataToClientState(
+ mPendingFillRequest, mPendingInlineSuggestionsRequest, id);
}
- mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
- mPendingFillRequest.getFlags(), mClient.asBinder());
+ mSecondaryProviderHandler.onFillRequest(
+ mPendingFillRequest, mPendingFillRequest.getFlags(), mClient.asBinder());
} else if (mRemoteFillService != null) {
if (mIsPrimaryCredential) {
- mPendingFillRequest = addCredentialManagerDataToClientState(
- mPendingFillRequest,
- mPendingInlineSuggestionsRequest, id);
- mRemoteFillService.onFillCredentialRequest(mPendingFillRequest,
- mClient.asBinder());
+ mPendingFillRequest =
+ addCredentialManagerDataToClientState(
+ mPendingFillRequest, mPendingInlineSuggestionsRequest, id);
+ mRemoteFillService.onFillCredentialRequest(
+ mPendingFillRequest, mClient.asBinder());
} else {
mRemoteFillService.onFillRequest(mPendingFillRequest);
}
@@ -813,8 +869,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public void onHandleAssistData(Bundle resultData) throws RemoteException {
if (mRemoteFillService == null) {
- wtf(null, "onHandleAssistData() called without a remote service. "
- + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
+ wtf(
+ null,
+ "onHandleAssistData() called without a remote service. "
+ + "mForAugmentedAutofillOnly: %s",
+ mSessionFlags.mAugmentedAutofillOnly);
return;
}
// Keeps to prevent it is cleared on multiple threads.
@@ -824,7 +883,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
- final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class);
+ final AssistStructure structure =
+ resultData.getParcelable(
+ ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class);
if (structure == null) {
Slog.e(TAG, "No assist structure - app might have crashed providing it");
return;
@@ -852,13 +913,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
try {
structure.ensureDataForAutofill();
} catch (RuntimeException e) {
- wtf(e, "Exception lazy loading assist structure for %s: %s",
- structure.getActivityComponent(), e);
+ wtf(
+ e,
+ "Exception lazy loading assist structure for %s: %s",
+ structure.getActivityComponent(),
+ e);
return;
}
- final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure,
- /* autofillableOnly= */false);
+ final ArrayList<AutofillId> ids =
+ Helper.getAutofillIds(structure, /* autofillableOnly= */ false);
for (int i = 0; i < ids.size(); i++) {
ids.get(i).setSessionId(Session.this.id);
}
@@ -868,8 +932,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mCompatMode) {
// Sanitize URL bar, if needed
- final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode(
- mComponentName.getPackageName());
+ final String[] urlBarIds =
+ mService.getUrlBarResourceIdsForCompatMode(
+ mComponentName.getPackageName());
if (sDebug) {
Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds));
}
@@ -878,11 +943,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mUrlBar != null) {
final AutofillId urlBarId = mUrlBar.getAutofillId();
if (sDebug) {
- Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain "
- + mUrlBar.getWebDomain());
+ Slog.d(
+ TAG,
+ "Setting urlBar as id="
+ + urlBarId
+ + " and domain "
+ + mUrlBar.getWebDomain());
}
- final ViewState viewState = new ViewState(urlBarId, Session.this,
- ViewState.STATE_URL_BAR, mIsPrimaryCredential);
+ final ViewState viewState =
+ new ViewState(
+ urlBarId,
+ Session.this,
+ ViewState.STATE_URL_BAR,
+ mIsPrimaryCredential);
mViewStates.put(urlBarId, viewState);
}
}
@@ -907,11 +980,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final List<String> hints = getTypeHintsForProvider();
mDelayedFillPendingIntent = createPendingIntent(requestId);
- request = new FillRequest(requestId, contexts, hints, mClientState, flags,
- /*inlineSuggestionsRequest=*/ null,
- /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null
- ? null
- : mDelayedFillPendingIntent.getIntentSender());
+ request =
+ new FillRequest(
+ requestId,
+ contexts,
+ hints,
+ mClientState,
+ flags,
+ /* inlineSuggestionsRequest= */ null,
+ /* delayedFillIntentSender= */ mDelayedFillPendingIntent == null
+ ? null
+ : mDelayedFillPendingIntent.getIntentSender());
mPendingFillRequest = request;
maybeRequestFillLocked();
@@ -930,39 +1009,49 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
void processDelayedFillLocked(int requestId, FillResponse response) {
if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) {
- Slog.v(TAG, "processDelayedFillLocked: "
- + "calling onFillRequestSuccess with new response");
- onFillRequestSuccess(requestId, response,
- mService.getServicePackageName(), mLastFillRequest.getFlags());
+ Slog.v(
+ TAG,
+ "processDelayedFillLocked: "
+ + "calling onFillRequestSuccess with new response");
+ onFillRequestSuccess(
+ requestId,
+ response,
+ mService.getServicePackageName(),
+ mLastFillRequest.getFlags());
}
}
}
- private FillRequest addCredentialManagerDataToClientState(FillRequest pendingFillRequest,
- InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
+ private FillRequest addCredentialManagerDataToClientState(
+ FillRequest pendingFillRequest,
+ InlineSuggestionsRequest pendingInlineSuggestionsRequest,
+ int sessionId) {
if (pendingFillRequest.getClientState() == null) {
- pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
- pendingFillRequest.getFillContexts(),
- pendingFillRequest.getHints(),
- new Bundle(),
- pendingFillRequest.getFlags(),
- pendingInlineSuggestionsRequest,
- pendingFillRequest.getDelayedFillIntentSender());
+ pendingFillRequest =
+ new FillRequest(
+ pendingFillRequest.getId(),
+ pendingFillRequest.getFillContexts(),
+ pendingFillRequest.getHints(),
+ new Bundle(),
+ pendingFillRequest.getFlags(),
+ pendingInlineSuggestionsRequest,
+ pendingFillRequest.getDelayedFillIntentSender());
}
pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
- ResultReceiver resultReceiver = constructCredentialManagerCallback(
- pendingFillRequest.getId());
- pendingFillRequest.getClientState().putParcelable(
- CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver);
+ ResultReceiver resultReceiver =
+ constructCredentialManagerCallback(pendingFillRequest.getId());
+ pendingFillRequest
+ .getClientState()
+ .putParcelable(CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver);
return pendingFillRequest;
}
/**
- * Get the list of valid autofill hint types from Device flags
- * Returns empty list if PCC is off or no types available
- */
+ * Get the list of valid autofill hint types from Device flags Returns empty list if PCC is off
+ * or no types available
+ */
private List<String> getTypeHintsForProvider() {
if (!mService.isPccClassificationEnabled()) {
return Collections.EMPTY_LIST;
@@ -978,9 +1067,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return List.of(typeHints.split(PCC_HINTS_DELIMITER));
}
- /**
- * Assist Data Receiver for PCC
- */
+ /** Assist Data Receiver for PCC */
private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub {
@GuardedBy("mLock")
@@ -998,7 +1085,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
new WeakReference<>(Session.this);
remoteFieldClassificationService.onFieldClassificationRequest(
mClassificationState.mPendingFieldClassificationRequest,
- fieldClassificationServiceCallbacksWeakRef);
+ fieldClassificationServiceCallbacksWeakRef);
}
mClassificationState.onFieldClassificationRequestSent();
}
@@ -1006,26 +1093,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public void onHandleAssistData(Bundle resultData) throws RemoteException {
// TODO: add a check if pcc field classification service is present
- final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE,
- android.app.assist.AssistStructure.class);
+ final AssistStructure structure =
+ resultData.getParcelable(
+ ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class);
if (structure == null) {
- Slog.e(TAG, "No assist structure for pcc detection - "
- + "app might have crashed providing it");
+ Slog.e(
+ TAG,
+ "No assist structure for pcc detection - "
+ + "app might have crashed providing it");
return;
}
final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
if (receiverExtras == null) {
- Slog.e(TAG, "No receiver extras for pcc detection - "
- + "app might have crashed providing it");
+ Slog.e(
+ TAG,
+ "No receiver extras for pcc detection - "
+ + "app might have crashed providing it");
return;
}
final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
if (sVerbose) {
- Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": "
- + structure);
+ Slog.v(
+ TAG,
+ "New structure for PCC Detection: requestId "
+ + requestId
+ + ": "
+ + structure);
}
synchronized (mLock) {
@@ -1037,13 +1133,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
try {
structure.ensureDataForAutofill();
} catch (RuntimeException e) {
- wtf(e, "Exception lazy loading assist structure for %s: %s",
- structure.getActivityComponent(), e);
+ wtf(
+ e,
+ "Exception lazy loading assist structure for %s: %s",
+ structure.getActivityComponent(),
+ e);
return;
}
- final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure,
- /* autofillableOnly= */false);
+ final ArrayList<AutofillId> ids =
+ Helper.getAutofillIds(structure, /* autofillableOnly= */ false);
for (int i = 0; i < ids.size(); i++) {
ids.get(i).setSessionId(Session.this.id);
}
@@ -1066,13 +1165,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
PendingIntent pendingIntent;
final long identity = Binder.clearCallingIdentity();
try {
- Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android")
- .putExtra(EXTRA_REQUEST_ID, requestId);
- pendingIntent = PendingIntent.getBroadcast(
- mContext, this.id, intent,
- PendingIntent.FLAG_MUTABLE
- | PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT);
+ Intent intent =
+ new Intent(ACTION_DELAYED_FILL)
+ .setPackage("android")
+ .putExtra(EXTRA_REQUEST_ID, requestId);
+ pendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ this.id,
+ intent,
+ PendingIntent.FLAG_MUTABLE
+ | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1113,9 +1217,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- /**
- * Returns the ids of all entries in {@link #mViewStates} in the same order.
- */
+ /** Returns the ids of all entries in {@link #mViewStates} in the same order. */
@GuardedBy("mLock")
private AutofillId[] getIdsOfAllViewStatesLocked() {
final int numViewState = mViewStates.size();
@@ -1164,8 +1266,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
- * or {@code null} when not found on either of them.
+ * Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, or
+ * {@code null} when not found on either of them.
*/
@GuardedBy("mLock")
@Nullable
@@ -1180,16 +1282,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ArrayList<Session> previousSessions = mService.getPreviousSessionsLocked(this);
if (previousSessions != null) {
if (sDebug) {
- Slog.d(TAG, "findValueLocked(): looking on " + previousSessions.size()
- + " previous sessions for autofillId " + autofillId);
+ Slog.d(
+ TAG,
+ "findValueLocked(): looking on "
+ + previousSessions.size()
+ + " previous sessions for autofillId "
+ + autofillId);
}
for (int i = 0; i < previousSessions.size(); i++) {
final Session previousSession = previousSessions.get(i);
- final AutofillValue previousValue = previousSession
- .findValueFromThisSessionOnlyLocked(autofillId);
+ final AutofillValue previousValue =
+ previousSession.findValueFromThisSessionOnlyLocked(autofillId);
if (previousValue != null) {
- return getSanitizedValue(createSanitizers(previousSession.getSaveInfoLocked()),
- autofillId, previousValue);
+ return getSanitizedValue(
+ createSanitizers(previousSession.getSaveInfoLocked()),
+ autofillId,
+ previousValue);
}
}
}
@@ -1212,16 +1320,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
AutofillValue candidateSaveValue = state.getCandidateSaveValue();
if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
if (sDebug) {
- Slog.d(TAG, "findValueLocked(): current value for " + autofillId
- + " is empty, using candidateSaveValue instead.");
+ Slog.d(
+ TAG,
+ "findValueLocked(): current value for "
+ + autofillId
+ + " is empty, using candidateSaveValue instead.");
}
return candidateSaveValue;
}
}
if (value == null) {
if (sDebug) {
- Slog.d(TAG, "findValueLocked(): no current value for " + autofillId
- + ", checking value from previous fill contexts");
+ Slog.d(
+ TAG,
+ "findValueLocked(): no current value for "
+ + autofillId
+ + ", checking value from previous fill contexts");
value = getValueFromContextsLocked(autofillId);
}
}
@@ -1231,17 +1345,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Updates values of the nodes in the context's structure so that:
*
- * - proper node is focused
- * - autofillValue is sent back to service when it was previously autofilled
- * - autofillValue is sent in the view used to force a request
+ * <p>- proper node is focused - autofillValue is sent back to service when it was previously
+ * autofilled - autofillValue is sent in the view used to force a request
*
* @param fillContext The context to be filled
* @param flags The flags that started the session
*/
@GuardedBy("mLock")
private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
- final ViewNode[] nodes = fillContext
- .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
+ final ViewNode[] nodes =
+ fillContext.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
final int numViewState = mViewStates.size();
for (int i = 0; i < numViewState; i++) {
@@ -1250,7 +1363,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ViewNode node = nodes[i];
if (node == null) {
if (sVerbose) {
- Slog.v(TAG,
+ Slog.v(
+ TAG,
"fillContextWithAllowedValuesLocked(): no node for " + viewState.id);
}
continue;
@@ -1277,14 +1391,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- /**
- * Cancels the last request sent to the {@link #mRemoteFillService}.
- */
+ /** Cancels the last request sent to the {@link #mRemoteFillService}. */
@GuardedBy("mLock")
private void cancelCurrentRequestLocked() {
if (mRemoteFillService == null) {
- wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
- + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
+ wtf(
+ null,
+ "cancelCurrentRequestLocked() called without a remote service. "
+ + "mForAugmentedAutofillOnly: %s",
+ mSessionFlags.mAugmentedAutofillOnly);
return;
}
final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
@@ -1318,21 +1433,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private Optional<Integer> requestNewFillResponseLocked(
@NonNull ViewState viewState, int newState, int flags) {
boolean isSecondary = shouldRequestSecondaryProvider(flags);
- final FillResponse existingResponse = isSecondary
- ? viewState.getSecondaryResponse() : viewState.getResponse();
+ final FillResponse existingResponse =
+ isSecondary ? viewState.getSecondaryResponse() : viewState.getResponse();
mFillRequestEventLogger.startLogForNewRequest();
mRequestCount++;
mFillRequestEventLogger.maybeSetAppPackageUid(uid);
mFillRequestEventLogger.maybeSetFlags(mFlags);
- if(mPreviouslyFillDialogPotentiallyStarted) {
+ if (mPreviouslyFillDialogPotentiallyStarted) {
mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER);
} else {
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mFillRequestEventLogger.maybeSetRequestTriggerReason(
TRIGGER_REASON_EXPLICITLY_REQUESTED);
} else {
- mFillRequestEventLogger.maybeSetRequestTriggerReason(
- TRIGGER_REASON_NORMAL_TRIGGER);
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER);
}
}
if (existingResponse != null) {
@@ -1348,9 +1462,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionState = STATE_ACTIVE;
if (mSessionFlags.mAugmentedAutofillOnly || mRemoteFillService == null) {
if (sVerbose) {
- Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead "
- + "(mForAugmentedAutofillOnly=" + mSessionFlags.mAugmentedAutofillOnly
- + ", flags=" + flags + ")");
+ Slog.v(
+ TAG,
+ "requestNewFillResponse(): triggering augmented autofill instead "
+ + "(mForAugmentedAutofillOnly="
+ + mSessionFlags.mAugmentedAutofillOnly
+ + ", flags="
+ + flags
+ + ")");
}
mSessionFlags.mAugmentedAutofillOnly = true;
mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID);
@@ -1365,16 +1484,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Create a metrics log for the request
final int ordinal = mRequestLogs.size() + 1;
- final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal);
+ final LogMaker log =
+ newLogMaker(MetricsEvent.AUTOFILL_REQUEST)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal);
if (flags != 0) {
log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags);
}
mRequestLogs.put(requestId, log);
if (sVerbose) {
- Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId
- + ", flags=" + flags);
+ Slog.v(
+ TAG,
+ "Requesting structure for request #"
+ + ordinal
+ + " ,requestId="
+ + requestId
+ + ", flags="
+ + flags);
}
boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
mFillRequestEventLogger.maybeSetRequestId(requestId);
@@ -1406,11 +1532,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// is also not focused.
final RemoteInlineSuggestionRenderService remoteRenderService =
mService.getRemoteInlineSuggestionRenderServiceLocked();
- if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null
- && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
+ if (mSessionFlags.mInlineSupportedByService
+ && remoteRenderService != null
+ && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
- mAssistReceiver.newAutofillRequestLocked(viewState,
- /* isInlineRequest= */ true);
+ mAssistReceiver.newAutofillRequestLocked(
+ viewState, /* isInlineRequest= */ true);
if (inlineSuggestionsRequestConsumer != null) {
final int requestIdCopy = requestId;
final AutofillId focusedId = mCurrentViewId;
@@ -1423,8 +1550,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
requestIdCopy,
inlineSuggestionsRequestConsumer,
focusedId);
- RemoteCallback inlineSuggestionRendorInfoCallback = new RemoteCallback(
- inlineSuggestionRendorInfoCallbackOnResultListener, mHandler);
+ RemoteCallback inlineSuggestionRendorInfoCallback =
+ new RemoteCallback(
+ inlineSuggestionRendorInfoCallbackOnResultListener, mHandler);
remoteRenderService.getInlineSuggestionsRendererInfo(
inlineSuggestionRendorInfoCallback);
@@ -1437,6 +1565,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Now request the assist structure data.
requestAssistStructureLocked(requestId, flags);
+ if (mImproveFillDialogEnabled) {
+ // New request has been sent, so re-enable fill dialog.
+ // Fill dialog is eligible to be shown after each Fill request.
+ enableFillDialog();
+ }
+
return Optional.of(requestId);
}
@@ -1465,8 +1599,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
final long identity = Binder.clearCallingIdentity();
try {
- if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver,
- receiverExtras, mActivityToken, flags)) {
+ if (!ActivityTaskManager.getService()
+ .requestAutofillData(
+ mPccAssistReceiver, receiverExtras, mActivityToken, flags)) {
Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
}
} finally {
@@ -1483,8 +1618,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
final long identity = Binder.clearCallingIdentity();
try {
- if (!ActivityTaskManager.getService().requestAutofillData(mAssistReceiver,
- receiverExtras, mActivityToken, flags)) {
+ if (!ActivityTaskManager.getService()
+ .requestAutofillData(
+ mAssistReceiver, receiverExtras, mActivityToken, flags)) {
Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
}
} finally {
@@ -1495,13 +1631,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
- @NonNull Context context, @NonNull Handler handler, int userId, @NonNull Object lock,
- int sessionId, int taskId, int uid, @NonNull IBinder activityToken,
- @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
- @NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName,
- @NonNull ComponentName componentName, boolean compatMode,
- boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags,
+ Session(
+ @NonNull AutofillManagerServiceImpl service,
+ @NonNull AutoFillUI ui,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ int userId,
+ @NonNull Object lock,
+ int sessionId,
+ int taskId,
+ int uid,
+ @NonNull IBinder activityToken,
+ @NonNull IBinder client,
+ boolean hasCallback,
+ @NonNull LocalLog uiLatencyHistory,
+ @NonNull LocalLog wtfHistory,
+ @Nullable ComponentName serviceComponentName,
+ @NonNull ComponentName componentName,
+ boolean compatMode,
+ boolean bindInstantServiceAllowed,
+ boolean forAugmentedAutofillOnly,
+ int flags,
@NonNull InputMethodManagerInternal inputMethodManagerInternal,
boolean isPrimaryCredential) {
if (sessionId < 0) {
@@ -1533,22 +1683,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
primaryServiceComponentName = serviceComponentName;
secondaryServiceComponentName = mCredentialAutofillService;
}
- Slog.v(TAG, "Primary service component name: " + primaryServiceComponentName
- + ", secondary service component name: " + secondaryServiceComponentName);
-
- mRemoteFillService = primaryServiceComponentName == null ? null
- : new RemoteFillService(context, primaryServiceComponentName, userId, this,
- bindInstantServiceAllowed, mCredentialAutofillService);
- mSecondaryProviderHandler = secondaryServiceComponentName == null ? null
- : new SecondaryProviderHandler(context, userId, bindInstantServiceAllowed,
- this::onSecondaryFillResponse, secondaryServiceComponentName,
- mCredentialAutofillService);
+ Slog.v(
+ TAG,
+ "Primary service component name: "
+ + primaryServiceComponentName
+ + ", secondary service component name: "
+ + secondaryServiceComponentName);
+
+ mRemoteFillService =
+ primaryServiceComponentName == null
+ ? null
+ : new RemoteFillService(
+ context,
+ primaryServiceComponentName,
+ userId,
+ this,
+ bindInstantServiceAllowed,
+ mCredentialAutofillService);
+ mSecondaryProviderHandler =
+ secondaryServiceComponentName == null
+ ? null
+ : new SecondaryProviderHandler(
+ context,
+ userId,
+ bindInstantServiceAllowed,
+ this::onSecondaryFillResponse,
+ secondaryServiceComponentName,
+ mCredentialAutofillService);
mActivityToken = activityToken;
mHasCallback = hasCallback;
mUiLatencyHistory = uiLatencyHistory;
mWtfHistory = wtfHistory;
- int displayId = LocalServices.getService(ActivityTaskManagerInternal.class)
- .getDisplayId(activityToken);
+ int displayId =
+ LocalServices.getService(ActivityTaskManagerInternal.class)
+ .getDisplayId(activityToken);
mContext = Helper.getDisplayContext(context, displayId);
mComponentName = componentName;
mCompatMode = compatMode;
@@ -1557,8 +1725,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mStartTime = SystemClock.elapsedRealtime();
mLatencyBaseTime = mStartTime;
mRequestCount = 0;
- mPresentationStatsEventLogger = PresentationStatsEventLogger.createPresentationLog(
- sessionId, uid, mLatencyBaseTime);
+ mPresentationStatsEventLogger =
+ PresentationStatsEventLogger.createPresentationLog(
+ sessionId, uid, mLatencyBaseTime);
mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId);
mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId);
mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId);
@@ -1566,6 +1735,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSaveEventLogger = SaveEventLogger.forSessionId(sessionId, mLatencyBaseTime);
mIsPrimaryCredential = isPrimaryCredential;
mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
+ mImproveFillDialogEnabled =
+ improveFillDialogAconfig() && AutofillFeatureFlags.isImproveFillDialogEnabled();
+ mFillDialogTimeoutMs = AutofillFeatureFlags.getFillDialogTimeoutMs();
+ mFillDialogMinWaitAfterImeAnimationMs =
+ AutofillFeatureFlags.getFillDialogMinWaitAfterImeAnimationtEndMs();
synchronized (mLock) {
mSessionFlags = new SessionFlags();
@@ -1574,33 +1748,46 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
setClientLocked(client);
}
- mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
- userId, componentName, handler, mLock,
- new InlineFillUi.InlineUiEventCallback() {
- @Override
- public void notifyInlineUiShown(AutofillId autofillId) {
- notifyFillUiShown(autofillId);
- }
+ mInlineSessionController =
+ new AutofillInlineSessionController(
+ inputMethodManagerInternal,
+ userId,
+ componentName,
+ handler,
+ mLock,
+ new InlineFillUi.InlineUiEventCallback() {
+ @Override
+ public void notifyInlineUiShown(AutofillId autofillId) {
+ notifyFillUiShown(autofillId);
+ }
- @Override
- public void notifyInlineUiHidden(AutofillId autofillId) {
- notifyFillUiHidden(autofillId);
- }
- });
+ @Override
+ public void notifyInlineUiHidden(AutofillId autofillId) {
+ notifyFillUiHidden(autofillId);
+ }
+
+ @Override
+ public void onInputMethodStartInputView() {
+ // TODO(b/377868687): This method isn't called when IME doesn't
+ // support inline suggestion. Handle those cases as well.
+ onInputMethodStart();
+ }
+ });
- mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
+ mMetricsLogger.write(
+ newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
mLogViewEntered = false;
}
private ComponentName getCredentialAutofillService(Context context) {
ComponentName componentName = null;
- String credentialManagerAutofillCompName = context.getResources().getString(
- R.string.config_defaultCredentialManagerAutofillService);
+ String credentialManagerAutofillCompName =
+ context.getResources()
+ .getString(R.string.config_defaultCredentialManagerAutofillService);
if (credentialManagerAutofillCompName != null
&& !credentialManagerAutofillCompName.isEmpty()) {
- componentName = ComponentName.unflattenFromString(
- credentialManagerAutofillCompName);
+ componentName = ComponentName.unflattenFromString(credentialManagerAutofillCompName);
}
if (componentName == null) {
Slog.w(TAG, "Invalid CredentialAutofillService");
@@ -1614,7 +1801,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* @return The activity token
*/
@GuardedBy("mLock")
- @NonNull IBinder getActivityTokenLocked() {
+ @NonNull
+ IBinder getActivityTokenLocked() {
return mActivityToken;
}
@@ -1627,8 +1815,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#switchActivity() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#switchActivity() rejected - session: "
+ + id
+ + " destroyed");
return;
}
mActivityToken = newActivity;
@@ -1643,17 +1834,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private void setClientLocked(@NonNull IBinder client) {
unlinkClientVultureLocked();
mClient = IAutoFillManagerClient.Stub.asInterface(client);
- mClientVulture = () -> {
- synchronized (mLock) {
- Slog.d(TAG, "handling death of " + mActivityToken + " when saving="
- + mSessionFlags.mShowingSaveUi);
- if (mSessionFlags.mShowingSaveUi) {
- mUi.hideFillUi(this);
- } else {
- mUi.destroyAll(mPendingSaveUi, this, false);
- }
- }
- };
+ mClientVulture =
+ () -> {
+ synchronized (mLock) {
+ Slog.d(
+ TAG,
+ "handling death of "
+ + mActivityToken
+ + " when saving="
+ + mSessionFlags.mShowingSaveUi);
+ if (mSessionFlags.mShowingSaveUi) {
+ mUi.hideFillUi(this);
+ } else {
+ mUi.destroyAll(mPendingSaveUi, this, false);
+ }
+ }
+ };
try {
mClient.asBinder().linkToDeath(mClientVulture, 0);
} catch (RemoteException e) {
@@ -1676,8 +1872,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// FillServiceCallbacks
@Override
@SuppressWarnings("GuardedBy")
- public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
- @NonNull String servicePackageName, int requestFlags) {
+ public void onFillRequestSuccess(
+ int requestId,
+ @Nullable FillResponse response,
+ @NonNull String servicePackageName,
+ int requestFlags) {
final AutofillId[] fieldClassificationIds;
final LogMaker requestLog;
@@ -1701,8 +1900,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
getDetectionPreferenceForLogging());
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#onFillRequestSuccess() rejected - session: "
+ + id
+ + " destroyed");
mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
mFillResponseEventLogger.logAndEndEvent();
return;
@@ -1713,8 +1915,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// saveUi gets closed, the session will be destroyed and AutofillManager will reset
// its state. Processing the fill request will result in a great chance of corrupt
// state in Autofill.
- Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
- + id + " is showing saveUi");
+ Slog.w(
+ TAG,
+ "Call to Session#onFillRequestSuccess() rejected - session: "
+ + id
+ + " is showing saveUi");
mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
mFillResponseEventLogger.logAndEndEvent();
return;
@@ -1756,26 +1961,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mLogViewEntered) {
mLogViewEntered = false;
- mService.logViewEntered(id, null);
+ mService.logViewEntered(id, null, mCurrentViewId);
}
}
-
final long disableDuration = response.getDisableDuration();
final boolean autofillDisabled = disableDuration > 0;
if (autofillDisabled) {
final int flags = response.getFlags();
final boolean disableActivityOnly =
(flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0;
- notifyDisableAutofillToClient(disableDuration,
- disableActivityOnly ? mComponentName : null);
+ notifyDisableAutofillToClient(
+ disableDuration, disableActivityOnly ? mComponentName : null);
if (disableActivityOnly) {
- mService.disableAutofillForActivity(mComponentName, disableDuration,
- id, mCompatMode);
+ mService.disableAutofillForActivity(
+ mComponentName, disableDuration, id, mCompatMode);
} else {
- mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
- id, mCompatMode);
+ mService.disableAutofillForApp(
+ mComponentName.getPackageName(), disableDuration, id, mCompatMode);
}
synchronized (mLock) {
@@ -1786,17 +1990,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (triggerAugmentedAutofillLocked(requestFlags) != null) {
mSessionFlags.mAugmentedAutofillOnly = true;
if (sDebug) {
- Slog.d(TAG, "Service disabled autofill for " + mComponentName
- + ", but session is kept for augmented autofill only");
+ Slog.d(
+ TAG,
+ "Service disabled autofill for "
+ + mComponentName
+ + ", but session is kept for augmented autofill only");
}
return;
}
}
if (sDebug) {
- final StringBuilder message = new StringBuilder("Service disabled autofill for ")
+ final StringBuilder message =
+ new StringBuilder("Service disabled autofill for ")
.append(mComponentName)
- .append(": flags=").append(flags)
+ .append(": flags=")
+ .append(flags)
.append(", duration=");
TimeUtils.formatDuration(disableDuration, message);
Slog.d(TAG, message.toString());
@@ -1816,8 +2025,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (requestLog != null) {
- requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
- response.getDatasets() == null ? 0 : response.getDatasets().size());
+ requestLog.addTaggedData(
+ MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+ response.getDatasets() == null ? 0 : response.getDatasets().size());
if (fieldClassificationIds != null) {
requestLog.addTaggedData(
MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS,
@@ -1840,10 +2050,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
-
@GuardedBy("mLock")
- private void processResponseLockedForPcc(@NonNull FillResponse response,
- @Nullable Bundle newClientState, int flags) {
+ private void processResponseLockedForPcc(
+ @NonNull FillResponse response, @Nullable Bundle newClientState, int flags) {
if (DBG) {
Slog.d(TAG, "DBG: Initial response: " + response);
}
@@ -1851,9 +2060,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
response = getEffectiveFillResponse(response);
if (isEmptyResponse(response)) {
// Treat it as a null response.
- processNullResponseLocked(
- response != null ? response.getRequestId() : 0,
- flags);
+ processNullResponseLocked(response != null ? response.getRequestId() : 0, flags);
return;
}
if (DBG) {
@@ -1870,9 +2077,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return ((response.getDatasets() == null || response.getDatasets().isEmpty())
&& response.getAuthentication() == null
&& (saveInfo == null
- || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
- && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
- && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
+ || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
+ && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
+ && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
&& (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
}
}
@@ -1884,10 +2091,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer);
if (DBG) {
- Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: "
- + autofillProviderContainer);
+ Slog.d(
+ TAG,
+ "DBG: computeDatasetsForProviderAndUpdateContainer: "
+ + autofillProviderContainer);
}
- if (!mService.isPccClassificationEnabled()) {
+ if (!mService.isPccClassificationEnabled()) {
if (sVerbose) {
Slog.v(TAG, "PCC classification is disabled");
}
@@ -1897,10 +2106,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mClassificationState.mState != ClassificationState.STATE_RESPONSE
|| mClassificationState.mLastFieldClassificationResponse == null) {
if (sVerbose) {
- Slog.v(TAG, "PCC classification no last response:"
- + (mClassificationState.mLastFieldClassificationResponse == null)
- + " ,ineligible state="
- + (mClassificationState.mState != ClassificationState.STATE_RESPONSE));
+ Slog.v(
+ TAG,
+ "PCC classification no last response:"
+ + (mClassificationState.mLastFieldClassificationResponse
+ == null)
+ + " ,ineligible state="
+ + (mClassificationState.mState
+ != ClassificationState.STATE_RESPONSE));
}
return createShallowCopy(response, autofillProviderContainer);
}
@@ -1966,8 +2179,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
(int) (fillRequestReceivedRelativeTimestamp));
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#onSecondaryFillResponse() rejected - session: "
+ + id
+ + " destroyed");
mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
mFillResponseEventLogger.logAndEndEvent();
return;
@@ -1981,7 +2197,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSecondaryResponses = new SparseArray<>(2);
}
mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse);
- setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false,
+ setViewStatesLocked(
+ fillResponse,
+ ViewState.STATE_FILLABLE,
+ /* clearResponse= */ false,
/* isPrimary= */ false);
// Updates the UI, if necessary.
@@ -1997,16 +2216,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private FillResponse createShallowCopy(
FillResponse response, DatasetComputationContainer container) {
return FillResponse.shallowCopy(
- response,
- new ArrayList<>(container.mDatasets),
- getEligibleSaveInfo(response));
+ response, new ArrayList<>(container.mDatasets), getEligibleSaveInfo(response));
}
private SaveInfo getEligibleSaveInfo(FillResponse response) {
SaveInfo saveInfo = response.getSaveInfo();
- if (saveInfo == null || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds())
- || !ArrayUtils.isEmpty(saveInfo.getRequiredIds())
- || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) {
+ if (saveInfo == null
+ || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds())
+ || !ArrayUtils.isEmpty(saveInfo.getRequiredIds())
+ || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) {
return saveInfo;
}
synchronized (mLock) {
@@ -2019,12 +2237,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
ArraySet<AutofillId> ids = new ArraySet<>();
int saveType = saveInfo.getType();
if (saveType == SaveInfo.SAVE_DATA_TYPE_GENERIC) {
- for (Set<AutofillId> autofillIds: hintsToAutofillIdMap.values()) {
+ for (Set<AutofillId> autofillIds : hintsToAutofillIdMap.values()) {
ids.addAll(autofillIds);
}
} else {
Set<String> hints = HintsHelper.getHintsForSaveType(saveType);
- for (Map.Entry<String, Set<AutofillId>> entry: hintsToAutofillIdMap.entrySet()) {
+ for (Map.Entry<String, Set<AutofillId>> entry : hintsToAutofillIdMap.entrySet()) {
String hint = entry.getKey();
if (hints.contains(hint)) {
ids.addAll(entry.getValue());
@@ -2039,9 +2257,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- /**
- * A private class to hold & compute datasets to be shown
- */
+ /** A private class to hold & compute datasets to be shown */
private static class DatasetComputationContainer {
// List of all autofill ids that have a corresponding datasets
Set<AutofillId> mAutofillIds = new LinkedHashSet<>();
@@ -2103,9 +2319,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * Computes datasets that are eligible to be shown based on provider detections.
- * Datasets are populated in the provided container for them to be later merged with the
- * PCC eligible datasets based on preference strategy.
+ * Computes datasets that are eligible to be shown based on provider detections. Datasets are
+ * populated in the provided container for them to be later merged with the PCC eligible
+ * datasets based on preference strategy.
+ *
* @param response
* @param container
*/
@@ -2210,9 +2427,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * Computes datasets that are eligible to be shown based on PCC detections.
- * Datasets are populated in the provided container for them to be later merged with the
- * provider eligible datasets based on preference strategy.
+ * Computes datasets that are eligible to be shown based on PCC detections. Datasets are
+ * populated in the provided container for them to be later merged with the provider eligible
+ * datasets based on preference strategy.
+ *
* @param response
* @param container
*/
@@ -2267,14 +2485,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// For that, there has to be a datatype detected by PCC, and the dataset
// for that datatype provided by the provider.
AutofillId autofillId = dataset.getFieldIds().get(j);
- if (!mClassificationState.mClassificationCombinedHintsMap
- .containsKey(autofillId)) {
+ if (!mClassificationState.mClassificationCombinedHintsMap.containsKey(
+ autofillId)) {
additionalEligibleAutofillIds.add(autofillId);
additionalDatasetAutofillIds.add(autofillId);
// For each of the field, copy over values.
- copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
- fieldPresentations, fieldDialogPresentations,
- fieldInlinePresentations, fieldInlineTooltipPresentations,
+ copyFieldsFromDataset(
+ dataset,
+ j,
+ autofillId,
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
fieldFilters);
}
continue;
@@ -2292,9 +2517,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
eligibleAutofillIds.add(autofillId);
datasetAutofillIds.add(autofillId);
// For each of the field, copy over values.
- copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
- fieldPresentations, fieldDialogPresentations,
- fieldInlinePresentations, fieldInlineTooltipPresentations,
+ copyFieldsFromDataset(
+ dataset,
+ j,
+ autofillId,
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
fieldFilters);
}
}
@@ -2364,8 +2596,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
fieldPresentations.add(dataset.getFieldPresentation(index));
fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
- fieldInlineTooltipPresentations.add(
- dataset.getFieldInlineTooltipPresentation(index));
+ fieldInlineTooltipPresentations.add(dataset.getFieldInlineTooltipPresentation(index));
fieldFilters.add(dataset.getFilter(index));
}
@@ -2395,16 +2626,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
unregisterDelayedFillBroadcastLocked();
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
- + ") rejected - session: " + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#onFillRequestFailureOrTimeout(req="
+ + requestId
+ + ") rejected - session: "
+ + id
+ + " destroyed");
mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
mFillResponseEventLogger.logAndEndEvent();
return;
}
if (sDebug) {
- Slog.d(TAG, "finishing session due to service "
- + (timedOut ? "timeout" : "failure"));
+ Slog.d(
+ TAG,
+ "finishing session due to service " + (timedOut ? "timeout" : "failure"));
}
mService.resetLastResponse();
mLastFillDialogTriggerIds = null;
@@ -2418,12 +2655,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final int targetSdk = mService.getTargedSdkLocked();
if (targetSdk >= Build.VERSION_CODES.Q) {
showMessage = false;
- Slog.w(TAG, "onFillRequestFailureOrTimeout(): not showing '" + message
- + "' because service's targetting API " + targetSdk);
+ Slog.w(
+ TAG,
+ "onFillRequestFailureOrTimeout(): not showing '"
+ + message
+ + "' because service's targeting API "
+ + targetSdk);
}
if (message != null) {
- requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN,
- message.length());
+ requestLog.addTaggedData(
+ MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length());
}
}
@@ -2445,8 +2686,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
mFillResponseEventLogger.logAndEndEvent();
}
- notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED,
- /* autofillableIds= */ null);
+ notifyUnavailableToClient(
+ AutofillManager.STATE_UNKNOWN_FAILED, /* autofillableIds= */ null);
if (showMessage) {
getUiForShowing().showError(message, this);
}
@@ -2455,8 +2696,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// FillServiceCallbacks
@Override
- public void onSaveRequestSuccess(@NonNull String servicePackageName,
- @Nullable IntentSender intentSender) {
+ public void onSaveRequestSuccess(
+ @NonNull String servicePackageName, @Nullable IntentSender intentSender) {
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
// Log onSaveRequest result.
@@ -2464,16 +2705,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSaveEventLogger.maybeSetLatencySaveFinishMillis();
mSaveEventLogger.logAndEndEvent();
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#onSaveRequestSuccess() rejected - session: "
+ + id
+ + " destroyed");
return;
}
}
- LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
- .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
+ LogMaker log =
+ newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+ .setType(
+ intentSender == null
+ ? MetricsEvent.TYPE_SUCCESS
+ : MetricsEvent.TYPE_OPEN);
mMetricsLogger.write(log);
-
if (intentSender != null) {
if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
startIntentSenderAndFinishSession(intentSender);
@@ -2485,8 +2732,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// FillServiceCallbacks
@Override
- public void onSaveRequestFailure(@Nullable CharSequence message,
- @NonNull String servicePackageName) {
+ public void onSaveRequestFailure(
+ @Nullable CharSequence message, @NonNull String servicePackageName) {
boolean showMessage = !TextUtils.isEmpty(message);
synchronized (mLock) {
@@ -2495,28 +2742,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSaveEventLogger.maybeSetLatencySaveFinishMillis();
mSaveEventLogger.logAndEndEvent();
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#onSaveRequestFailure() rejected - session: "
+ + id
+ + " destroyed");
return;
}
if (showMessage) {
final int targetSdk = mService.getTargedSdkLocked();
if (targetSdk >= Build.VERSION_CODES.Q) {
showMessage = false;
- Slog.w(TAG, "onSaveRequestFailure(): not showing '" + message
- + "' because service's targetting API " + targetSdk);
+ Slog.w(
+ TAG,
+ "onSaveRequestFailure(): not showing '"
+ + message
+ + "' because service's targeting API "
+ + targetSdk);
}
}
}
final LogMaker log =
newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
- .setType(MetricsEvent.TYPE_FAILURE);
+ .setType(MetricsEvent.TYPE_FAILURE);
if (message != null) {
log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length());
}
mMetricsLogger.write(log);
-
if (showMessage) {
getUiForShowing().showError(message, this);
}
@@ -2525,8 +2778,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// FillServiceCallbacks
@Override
- public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
- convertCredentialResponse) {
+ public void onConvertCredentialRequestSuccess(
+ @NonNull ConvertCredentialResponse convertCredentialResponse) {
Dataset dataset = convertCredentialResponse.getDataset();
Bundle clientState = convertCredentialResponse.getClientState();
if (dataset != null) {
@@ -2534,15 +2787,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (clientState != null) {
requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID);
} else {
- Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this "
- + "would cause loss in logging.");
+ Slog.e(
+ TAG,
+ "onConvertCredentialRequestSuccess(): client state is null, this "
+ + "would cause loss in logging.");
}
// TODO: Add autofill related logging; consider whether to log the index
- fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET);
+ fill(requestId, /* datasetIndex= */ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET);
} else {
// TODO: Add logging to log this error case
- Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is "
- + "null");
+ Slog.e(
+ TAG,
+ "onConvertCredentialRequestSuccess(): dataset inside response is " + "null");
}
}
@@ -2550,11 +2806,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* Gets the {@link FillContext} for a request.
*
* @param requestId The id of the request
- *
* @return The context or {@code null} if there is no context
*/
@GuardedBy("mLock")
- @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
+ @Nullable
+ private FillContext getFillContextByRequestIdLocked(int requestId) {
if (mContexts == null) {
return null;
}
@@ -2582,19 +2838,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// AutoFillUiCallback
@Override
- public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras,
- int uiType) {
+ public void authenticate(
+ int requestId, int datasetIndex, IntentSender intent, Bundle extras, int uiType) {
if (sDebug) {
- Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
- + "; intentSender=" + intent);
+ Slog.d(
+ TAG,
+ "authenticate(): requestId="
+ + requestId
+ + "; datasetIdx="
+ + datasetIndex
+ + "; intentSender="
+ + intent);
}
final Intent fillInIntent;
synchronized (mLock) {
mPresentationStatsEventLogger.maybeSetAuthenticationType(
- AUTHENTICATION_TYPE_FULL_AUTHENTICATION);
+ AUTHENTICATION_TYPE_FULL_AUTHENTICATION);
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#authenticate() rejected - session: " + id + " destroyed");
return;
}
fillInIntent = createAuthFillInIntentLocked(requestId, extras);
@@ -2602,15 +2865,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
forceRemoveFromServiceLocked();
return;
}
+ mService.setAuthenticationSelected(id, mClientState, uiType, mCurrentViewId);
}
- mService.setAuthenticationSelected(id, mClientState, uiType);
final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
- mHandler.sendMessage(obtainMessage(
- Session::startAuthentication,
- this, authenticationId, intent, fillInIntent,
- /* authenticateInline= */ uiType == UI_TYPE_INLINE));
+ mHandler.sendMessage(
+ obtainMessage(
+ Session::startAuthentication,
+ this,
+ authenticationId,
+ intent,
+ fillInIntent,
+ /* authenticateInline= */ uiType == UI_TYPE_INLINE));
}
// AutoFillUiCallback
@@ -2618,14 +2885,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public void fill(int requestId, int datasetIndex, Dataset dataset, int uiType) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#fill() rejected - session: "
- + id + " destroyed");
+ Slog.w(TAG, "Call to Session#fill() rejected - session: " + id + " destroyed");
return;
}
}
- mHandler.sendMessage(obtainMessage(
- Session::autoFill,
- this, requestId, datasetIndex, dataset, true, uiType));
+ mHandler.sendMessage(
+ obtainMessage(
+ Session::autoFill, this, requestId, datasetIndex, dataset, true, uiType));
}
// AutoFillUiCallback
@@ -2633,15 +2899,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public void save() {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#save() rejected - session: "
- + id + " destroyed");
+ Slog.w(TAG, "Call to Session#save() rejected - session: " + id + " destroyed");
return;
}
mSaveEventLogger.maybeSetLatencySaveRequestMillis();
}
- mHandler.sendMessage(obtainMessage(
- AutofillManagerServiceImpl::handleSessionSave,
- mService, this));
+ mHandler.sendMessage(
+ obtainMessage(AutofillManagerServiceImpl::handleSessionSave, mService, this));
}
// AutoFillUiCallback
@@ -2650,13 +2914,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#cancelSave() rejected - session: " + id + " destroyed");
return;
}
}
- mHandler.sendMessage(obtainMessage(
- Session::removeFromService, this));
+ mHandler.sendMessage(obtainMessage(Session::removeFromService, this));
}
// AutofillUiCallback
@@ -2676,7 +2940,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (!mLoggedInlineDatasetShown) {
// Chip inflation already logged, do not log again.
// This is needed because every chip inflation will call this.
- mService.logDatasetShown(this.id, mClientState, uiType);
+ mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
}
mLoggedInlineDatasetShown = true;
@@ -2684,7 +2948,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
// Explicitly sets maybeSetSuggestionPresentedTimestampMs
mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
- mService.logDatasetShown(this.id, mClientState, uiType);
+ mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
}
}
@@ -2692,26 +2956,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// AutoFillUiCallback
@Override
- public void requestShowFillUi(AutofillId id, int width, int height,
- IAutofillWindowPresenter presenter) {
+ public void requestShowFillUi(
+ AutofillId id, int width, int height, IAutofillWindowPresenter presenter) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#requestShowFillUi() rejected - session: "
+ + id
+ + " destroyed");
return;
}
if (id.equals(mCurrentViewId)) {
try {
final ViewState view = mViewStates.get(id);
- mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(),
- presenter);
+ mClient.requestShowFillUi(
+ this.id, id, width, height, view.getVirtualBounds(), presenter);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting to show fill UI", e);
}
} else {
if (sDebug) {
- Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view ("
- + mCurrentViewId + ") anymore");
+ Slog.d(
+ TAG,
+ "Do not show full UI on "
+ + id
+ + " as it is not the current view ("
+ + mCurrentViewId
+ + ") anymore");
}
}
}
@@ -2722,8 +2994,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#dispatchUnhandledKey() rejected - session: "
+ + id
+ + " destroyed");
return;
}
if (id.equals(mCurrentViewId)) {
@@ -2733,8 +3008,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.e(TAG, "Error requesting to dispatch unhandled key", e);
}
} else {
- Slog.w(TAG, "Do not dispatch unhandled key on " + id
- + " as it is not the current view (" + mCurrentViewId + ") anymore");
+ Slog.w(
+ TAG,
+ "Do not dispatch unhandled key on "
+ + id
+ + " as it is not the current view ("
+ + mCurrentViewId
+ + ") anymore");
}
}
}
@@ -2790,17 +3070,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public void startIntentSender(IntentSender intentSender, Intent intent) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#startIntentSender() rejected - session: "
+ + id
+ + " destroyed");
return;
}
if (intent == null) {
removeFromServiceLocked();
}
}
- mHandler.sendMessage(obtainMessage(
- Session::doStartIntentSender,
- this, intentSender, intent));
+ mHandler.sendMessage(
+ obtainMessage(Session::doStartIntentSender, this, intentSender, intent));
}
// AutoFillUiCallback
@@ -2852,6 +3134,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ private void onInputMethodStart() {
+ synchronized (mLock) {
+ mLastInputStartTime = SystemClock.elapsedRealtime();
+ }
+ }
+
private void doStartIntentSender(IntentSender intentSender, Intent intent) {
try {
synchronized (mLock) {
@@ -2865,13 +3153,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
void setAuthenticationResultLocked(Bundle data, int authenticationId) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#setAuthenticationResultLocked() rejected - session: "
+ + id
+ + " destroyed");
return;
}
if (sDebug) {
- Slog.d(TAG, "setAuthenticationResultLocked(): id= " + authenticationId
- + ", data=" + data);
+ Slog.d(
+ TAG,
+ "setAuthenticationResultLocked(): id= " + authenticationId + ", data=" + data);
}
final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
@@ -2890,9 +3182,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
removeFromService();
return;
}
- final FillResponse authenticatedResponse = mRequestId.isSecondaryProvider(requestId)
- ? mSecondaryResponses.get(requestId)
- : mResponses.get(requestId);
+ final FillResponse authenticatedResponse =
+ mRequestId.isSecondaryProvider(requestId)
+ ? mSecondaryResponses.get(requestId)
+ : mResponses.get(requestId);
if (authenticatedResponse == null || data == null) {
Slog.w(TAG, "no authenticated response");
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
@@ -2902,8 +3195,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
- final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(
- authenticationId);
+ final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(authenticationId);
Dataset dataset = null;
// Authenticated a dataset - reset view state regardless if we got a response or a dataset
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
@@ -2922,33 +3214,45 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionFlags.mExpiredResponse = false;
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
- final GetCredentialException exception = data.getSerializable(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
- GetCredentialException.class);
+ final GetCredentialException exception =
+ data.getSerializable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+ GetCredentialException.class);
final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
if (sDebug) {
- Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
- + ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
- }
- if (Flags.autofillCredmanDevIntegration() && exception != null
+ Slog.d(
+ TAG,
+ "setAuthenticationResultLocked(): result="
+ + result
+ + ", clientState="
+ + newClientState
+ + ", authenticationId="
+ + authenticationId);
+ }
+ if (Flags.autofillCredmanDevIntegration()
+ && exception != null
&& !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) {
if (dataset != null && dataset.getFieldIds().size() == 1) {
if (sDebug) {
- Slog.d(TAG, "setAuthenticationResultLocked(): result returns with"
- + "Credential Manager Exception");
+ Slog.d(
+ TAG,
+ "setAuthenticationResultLocked(): result returns with"
+ + "Credential Manager Exception");
}
AutofillId autofillId = dataset.getFieldIds().get(0);
- sendCredentialManagerResponseToApp(/*response=*/ null,
- (GetCredentialException) exception, autofillId);
+ sendCredentialManagerResponseToApp(
+ /* response= */ null, (GetCredentialException) exception, autofillId);
}
return;
}
if (result instanceof FillResponse) {
if (sDebug) {
- Slog.d(TAG, "setAuthenticationResultLocked(): received FillResponse from"
- + " authentication flow");
+ Slog.d(
+ TAG,
+ "setAuthenticationResultLocked(): received FillResponse from"
+ + " authentication flow");
}
logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED);
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
@@ -2963,33 +3267,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (dataset != null && dataset.getFieldIds().size() == 1) {
AutofillId autofillId = dataset.getFieldIds().get(0);
if (sDebug) {
- Slog.d(TAG, "Received GetCredentialResponse from authentication flow,"
- + "for autofillId: " + autofillId);
+ Slog.d(
+ TAG,
+ "Received GetCredentialResponse from authentication flow,"
+ + "for autofillId: "
+ + autofillId);
}
- sendCredentialManagerResponseToApp(response,
- /*exception=*/ null, autofillId);
+ sendCredentialManagerResponseToApp(response, /* exception= */ null, autofillId);
}
} else if (Flags.autofillCredmanIntegration()) {
- Dataset datasetFromCredentialResponse = getDatasetFromCredentialResponse(
- (GetCredentialResponse) result);
+ Dataset datasetFromCredentialResponse =
+ getDatasetFromCredentialResponse((GetCredentialResponse) result);
if (datasetFromCredentialResponse != null) {
- autoFill(requestId, datasetIdx, datasetFromCredentialResponse,
- false, UI_TYPE_UNKNOWN);
+ autoFill(
+ requestId,
+ datasetIdx,
+ datasetFromCredentialResponse,
+ false,
+ UI_TYPE_UNKNOWN);
}
}
} else if (result instanceof Dataset) {
if (sDebug) {
- Slog.d(TAG, "setAuthenticationResultLocked(): received Dataset from"
- + " authentication flow");
+ Slog.d(
+ TAG,
+ "setAuthenticationResultLocked(): received Dataset from"
+ + " authentication flow");
}
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
- logAuthenticationStatusLocked(requestId,
- MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
+ logAuthenticationStatusLocked(
+ requestId, MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_SUCCESS);
if (newClientState != null) {
- if (sDebug)
- Slog.d(TAG, "Updating client state from auth dataset");
+ if (sDebug) Slog.d(TAG, "Updating client state from auth dataset");
mClientState = newClientState;
}
Dataset datasetFromResult = getEffectiveDatasetForAuthentication((Dataset) result);
@@ -2999,10 +3310,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
autoFill(requestId, datasetIdx, datasetFromResult, false, UI_TYPE_UNKNOWN);
} else {
- Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id "
- + authenticationId);
- logAuthenticationStatusLocked(requestId,
- MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
+ Slog.w(
+ TAG,
+ "invalid index ("
+ + datasetIdx
+ + ") for authentication id "
+ + authenticationId);
+ logAuthenticationStatusLocked(
+ requestId, MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_FAILURE);
}
@@ -3010,8 +3325,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (result != null) {
Slog.w(TAG, "service returned invalid auth type: " + result);
}
- logAuthenticationStatusLocked(requestId,
- MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
+ logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_FAILURE);
processNullResponseLocked(requestId, 0);
@@ -3036,8 +3350,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.d(TAG, "DBG: authenticated effective response: " + response);
}
if (response == null || response.getDatasets().size() == 0) {
- Log.wtf(TAG, "No datasets in fill response on authentication. response = "
- + (response == null ? "null" : response.toString()));
+ Log.wtf(
+ TAG,
+ "No datasets in fill response on authentication. response = "
+ + (response == null ? "null" : response.toString()));
return authenticatedDataset;
}
List<Dataset> datasets = response.getDatasets();
@@ -3047,8 +3363,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
for (Dataset dataset : datasets) {
if (!dataset.getFieldIds().isEmpty()) {
for (int i = 0; i < dataset.getFieldIds().size(); i++) {
- builder.setField(dataset.getFieldIds().get(i),
- new Field.Builder().setValue(dataset.getFieldValues().get(i))
+ builder.setField(
+ dataset.getFieldIds().get(i),
+ new Field.Builder()
+ .setValue(dataset.getFieldValues().get(i))
.build());
}
}
@@ -3063,12 +3381,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * Returns whether the dataset returned from the authentication result is ephemeral or not.
- * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
- * information.
+ * Returns whether the dataset returned from the authentication result is ephemeral or not. See
+ * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more information.
*/
- private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset,
- @NonNull Bundle authResultData) {
+ private static boolean isAuthResultDatasetEphemeral(
+ @Nullable Dataset oldDataset, @NonNull Bundle authResultData) {
if (authResultData.containsKey(
AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
return authResultData.getBoolean(
@@ -3079,11 +3396,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* A dataset can potentially have multiple fields, and it's possible that some of the fields'
- * has inline presentation and some don't. It's also possible that some of the fields'
- * inline presentation is pinned and some isn't. So the concept of whether a dataset is
- * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a
- * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient
- * for most of the cases.
+ * has inline presentation and some don't. It's also possible that some of the fields' inline
+ * presentation is pinned and some isn't. So the concept of whether a dataset is pinned or not
+ * is ill-defined. Here we say a dataset is pinned if any of the field has a pinned inline
+ * presentation in the dataset. It's not ideal but hopefully it is sufficient for most of the
+ * cases.
*/
private static boolean isPinnedDataset(@Nullable Dataset dataset) {
if (dataset != null && dataset.getFieldIds() != null) {
@@ -3100,16 +3417,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) {
- final Dataset dataset = (data == null) ? null :
- data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT, android.service.autofill.Dataset.class);
+ final Dataset dataset =
+ (data == null)
+ ? null
+ : data.getParcelable(
+ AutofillManager.EXTRA_AUTHENTICATION_RESULT,
+ android.service.autofill.Dataset.class);
if (sDebug) {
- Slog.d(TAG, "Auth result for augmented autofill: sessionId=" + id
- + ", authId=" + authId + ", dataset=" + dataset);
- }
- final AutofillId fieldId = (dataset != null && dataset.getFieldIds().size() == 1)
- ? dataset.getFieldIds().get(0) : null;
- final AutofillValue value = (dataset != null && dataset.getFieldValues().size() == 1)
- ? dataset.getFieldValues().get(0) : null;
+ Slog.d(
+ TAG,
+ "Auth result for augmented autofill: sessionId="
+ + id
+ + ", authId="
+ + authId
+ + ", dataset="
+ + dataset);
+ }
+ final AutofillId fieldId =
+ (dataset != null && dataset.getFieldIds().size() == 1)
+ ? dataset.getFieldIds().get(0)
+ : null;
+ final AutofillValue value =
+ (dataset != null && dataset.getFieldValues().size() == 1)
+ ? dataset.getFieldValues().get(0)
+ : null;
final ClipData content = (dataset != null) ? dataset.getFieldContent() : null;
if (fieldId == null || (value == null && content == null)) {
if (sDebug) {
@@ -3154,8 +3485,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Fill the value into the field.
if (sDebug) {
- Slog.d(TAG, "Filling after auth: fieldId=" + fieldId + ", value=" + value
- + ", content=" + content);
+ Slog.d(
+ TAG,
+ "Filling after auth: fieldId="
+ + fieldId
+ + ", value="
+ + value
+ + ", content="
+ + content);
}
try {
if (content != null) {
@@ -3164,8 +3501,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mClient.autofill(id, dataset.getFieldIds(), dataset.getFieldValues(), true);
}
} catch (RemoteException e) {
- Slog.w(TAG, "Error filling after auth: fieldId=" + fieldId + ", value=" + value
- + ", content=" + content, e);
+ Slog.w(
+ TAG,
+ "Error filling after auth: fieldId="
+ + fieldId
+ + ", value="
+ + value
+ + ", content="
+ + content,
+ e);
}
// Clear the suggestions since the user already accepted one of them.
@@ -3175,8 +3519,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
void setHasCallbackLocked(boolean hasIt) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#setHasCallbackLocked() rejected - session: "
+ + id
+ + " destroyed");
return;
}
mHasCallback = hasIt;
@@ -3185,9 +3532,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
@Nullable
private FillResponse getLastResponseLocked(@Nullable String logPrefixFmt) {
- final String logPrefix = sDebug && logPrefixFmt != null
- ? String.format(logPrefixFmt, this.id)
- : null;
+ final String logPrefix =
+ sDebug && logPrefixFmt != null ? String.format(logPrefixFmt, this.id) : null;
if (mContexts == null) {
if (logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
return null;
@@ -3204,16 +3550,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final int lastResponseIdx = getLastResponseIndexLocked();
if (lastResponseIdx < 0) {
if (logPrefix != null) {
- Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
- + ", mViewStates=" + mViewStates);
+ Slog.w(
+ TAG,
+ logPrefix
+ + ": did not get last response. mResponses="
+ + mResponses
+ + ", mViewStates="
+ + mViewStates);
}
return null;
}
final FillResponse response = mResponses.valueAt(lastResponseIdx);
if (sVerbose && logPrefix != null) {
- Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
- + ", mViewStates=" + mViewStates);
+ Slog.v(
+ TAG,
+ logPrefix
+ + ": mResponses="
+ + mResponses
+ + ", mContexts="
+ + mContexts
+ + ", mViewStates="
+ + mViewStates);
}
return response;
}
@@ -3232,9 +3590,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * Get statistic information of save info in current session. Specifically
- * 1. how many save info the current session has.
- * 2. How many distinct save data types current session has.
+ * Get statistic information of save info in current session. Specifically 1. how many save info
+ * the current session has. 2. How many distinct save data types current session has.
*
* @return SaveInfoStats returns the above two number in a SaveInfoStats object
*/
@@ -3255,12 +3612,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*/
public void logContextCommitted() {
if (sVerbose) {
- Slog.v(TAG, "logContextCommitted (" + id + "): commit_reason:" + COMMIT_REASON_UNKNOWN
- + " no_save_reason:" + Event.NO_SAVE_UI_REASON_NONE);
- }
- mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
- Event.NO_SAVE_UI_REASON_NONE,
- COMMIT_REASON_UNKNOWN));
+ Slog.v(
+ TAG,
+ "logContextCommitted ("
+ + id
+ + "): commit_reason:"
+ + COMMIT_REASON_UNKNOWN
+ + " no_save_reason:"
+ + Event.NO_SAVE_UI_REASON_NONE);
+ }
+ mHandler.sendMessage(
+ obtainMessage(
+ Session::handleLogContextCommitted,
+ this,
+ Event.NO_SAVE_UI_REASON_NONE,
+ COMMIT_REASON_UNKNOWN));
synchronized (mLock) {
logAllEventsLocked(COMMIT_REASON_UNKNOWN);
}
@@ -3274,16 +3640,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* @param saveDialogNotShowReason The reason why a save dialog was not shown.
* @param commitReason The reason why context is committed.
*/
-
@GuardedBy("mLock")
- public void logContextCommittedLocked(@NoSaveReason int saveDialogNotShowReason,
- @AutofillCommitReason int commitReason) {
+ public void logContextCommittedLocked(
+ @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) {
if (sVerbose) {
- Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason
- + " no_save_reason:" + saveDialogNotShowReason);
- }
- mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
- saveDialogNotShowReason, commitReason));
+ Slog.v(
+ TAG,
+ "logContextCommittedLocked ("
+ + id
+ + "): commit_reason:"
+ + commitReason
+ + " no_save_reason:"
+ + saveDialogNotShowReason);
+ }
+ mHandler.sendMessage(
+ obtainMessage(
+ Session::handleLogContextCommitted,
+ this,
+ saveDialogNotShowReason,
+ commitReason));
mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
@@ -3295,8 +3670,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE);
}
- private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason,
- @AutofillCommitReason int commitReason) {
+ private void handleLogContextCommitted(
+ @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) {
final FillResponse lastResponse;
synchronized (mLock) {
lastResponse = getLastResponseLocked("logContextCommited(%s)");
@@ -3326,31 +3701,42 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Sets field classification scores
if (userData != null && fcStrategy != null) {
- logFieldClassificationScore(fcStrategy, userData, saveDialogNotShowReason,
- commitReason);
+ logFieldClassificationScore(
+ fcStrategy, userData, saveDialogNotShowReason, commitReason);
} else {
logContextCommitted(null, null, saveDialogNotShowReason, commitReason);
}
}
- private void logContextCommitted(@Nullable ArrayList<AutofillId> detectedFieldIds,
+ private void logContextCommitted(
+ @Nullable ArrayList<AutofillId> detectedFieldIds,
@Nullable ArrayList<FieldClassification> detectedFieldClassifications,
@NoSaveReason int saveDialogNotShowReason,
@AutofillCommitReason int commitReason) {
synchronized (mLock) {
- logContextCommittedLocked(detectedFieldIds, detectedFieldClassifications,
- saveDialogNotShowReason, commitReason);
+ logContextCommittedLocked(
+ detectedFieldIds,
+ detectedFieldClassifications,
+ saveDialogNotShowReason,
+ commitReason);
}
}
@GuardedBy("mLock")
- private void logContextCommittedLocked(@Nullable ArrayList<AutofillId> detectedFieldIds,
+ private void logContextCommittedLocked(
+ @Nullable ArrayList<AutofillId> detectedFieldIds,
@Nullable ArrayList<FieldClassification> detectedFieldClassifications,
@NoSaveReason int saveDialogNotShowReason,
@AutofillCommitReason int commitReason) {
if (sVerbose) {
- Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason
- + " no_save_reason:" + saveDialogNotShowReason);
+ Slog.v(
+ TAG,
+ "logContextCommittedLocked ("
+ + id
+ + "): commit_reason:"
+ + commitReason
+ + " no_save_reason:"
+ + saveDialogNotShowReason);
}
final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)");
if (lastResponse == null) return;
@@ -3423,8 +3809,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue currentValue = viewState.getCurrentValue();
if (autofilledValue != null && autofilledValue.equals(currentValue)) {
if (sDebug) {
- Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
- + " because it has same value that was autofilled");
+ Slog.d(
+ TAG,
+ "logContextCommitted(): ignoring changed "
+ + viewState
+ + " because it has same value that was autofilled");
}
continue;
}
@@ -3442,8 +3831,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue currentValue = viewState.getCurrentValue();
if (currentValue == null) {
if (sDebug) {
- Slog.d(TAG, "logContextCommitted(): skipping view without current "
- + "value ( " + viewState + ")");
+ Slog.d(
+ TAG,
+ "logContextCommitted(): skipping view without current "
+ + "value ( "
+ + viewState
+ + ")");
}
continue;
}
@@ -3455,7 +3848,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final List<Dataset> datasets = response.getDatasets();
if (datasets == null || datasets.isEmpty()) {
if (sVerbose) {
- Slog.v(TAG, "logContextCommitted() no datasets at " + j);
+ Slog.v(TAG, "logContextCommitted() no datasets at " + j);
}
} else {
for (int k = 0; k < datasets.size(); k++) {
@@ -3463,8 +3856,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final String datasetId = dataset.getId();
if (datasetId == null) {
if (sVerbose) {
- Slog.v(TAG, "logContextCommitted() skipping idless "
- + "dataset " + dataset);
+ Slog.v(
+ TAG,
+ "logContextCommitted() skipping idless "
+ + "dataset "
+ + dataset);
}
} else {
final ArrayList<AutofillValue> values =
@@ -3473,9 +3869,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue candidate = values.get(l);
if (currentValue.equals(candidate)) {
if (sDebug) {
- Slog.d(TAG, "field " + viewState.id + " was "
- + "manually filled with value set by "
- + "dataset " + datasetId);
+ Slog.d(
+ TAG,
+ "field "
+ + viewState.id
+ + " was manually filled with"
+ + " value set by dataset "
+ + datasetId);
}
if (manuallyFilledIds == null) {
manuallyFilledIds = new ArrayMap<>();
@@ -3524,10 +3924,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
- changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
- manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
- mComponentName, mCompatMode, saveDialogNotShowReason);
+ mService.logContextCommittedLocked(
+ id,
+ mClientState,
+ mSelectedDatasetIds,
+ ignoredDatasets,
+ changedFieldIds,
+ changedDatasetIds,
+ manuallyFilledFieldIds,
+ manuallyFilledDatasetIds,
+ detectedFieldIds,
+ detectedFieldClassifications,
+ mComponentName,
+ mCompatMode,
+ saveDialogNotShowReason);
mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason);
@@ -3537,7 +3947,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
* {@code fieldId} based on its {@code currentValue} and {@code userData}.
*/
- private void logFieldClassificationScore(@NonNull FieldClassificationStrategy fcStrategy,
+ private void logFieldClassificationScore(
+ @NonNull FieldClassificationStrategy fcStrategy,
@NonNull FieldClassificationUserData userData,
@NoSaveReason int saveDialogNotShowReason,
@AutofillCommitReason int commitReason) {
@@ -3555,16 +3966,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) {
final int valuesLength = userValues == null ? -1 : userValues.length;
final int idsLength = categoryIds == null ? -1 : categoryIds.length;
- Slog.w(TAG, "setScores(): user data mismatch: values.length = "
- + valuesLength + ", ids.length = " + idsLength);
+ Slog.w(
+ TAG,
+ "setScores(): user data mismatch: values.length = "
+ + valuesLength
+ + ", ids.length = "
+ + idsLength);
return;
}
final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize);
- final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
- maxFieldsSize);
+ final ArrayList<FieldClassification> detectedFieldClassifications =
+ new ArrayList<>(maxFieldsSize);
final Collection<ViewState> viewStates;
synchronized (mLock) {
@@ -3583,33 +3998,49 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// Then use the results, asynchronously
- final RemoteCallback callback = new RemoteCallback(
- new LogFieldClassificationScoreOnResultListener(
- this,
- saveDialogNotShowReason,
- commitReason,
- viewsSize,
- autofillIds,
- userValues,
- categoryIds,
- detectedFieldIds,
- detectedFieldClassifications));
-
- fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
- defaultAlgorithm, defaultArgs, algorithms, args);
- }
-
- void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason,
- int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues,
- String[] categoryIds, ArrayList<AutofillId> detectedFieldIds,
+ final RemoteCallback callback =
+ new RemoteCallback(
+ new LogFieldClassificationScoreOnResultListener(
+ this,
+ saveDialogNotShowReason,
+ commitReason,
+ viewsSize,
+ autofillIds,
+ userValues,
+ categoryIds,
+ detectedFieldIds,
+ detectedFieldClassifications));
+
+ fcStrategy.calculateScores(
+ callback,
+ currentValues,
+ userValues,
+ categoryIds,
+ defaultAlgorithm,
+ defaultArgs,
+ algorithms,
+ args);
+ }
+
+ void handleLogFieldClassificationScore(
+ @Nullable Bundle result,
+ int saveDialogNotShowReason,
+ int commitReason,
+ int viewsSize,
+ AutofillId[] autofillIds,
+ String[] userValues,
+ String[] categoryIds,
+ ArrayList<AutofillId> detectedFieldIds,
ArrayList<FieldClassification> detectedFieldClassifications) {
if (result == null) {
if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
logContextCommitted(null, null, saveDialogNotShowReason, commitReason);
return;
}
- final Scores scores = result.getParcelable(EXTRA_SCORES,
- android.service.autofill.AutofillFieldClassificationService.Scores.class);
+ final Scores scores =
+ result.getParcelable(
+ EXTRA_SCORES,
+ android.service.autofill.AutofillFieldClassificationService.Scores.class);
if (scores == null) {
Slog.w(TAG, "No field classification score on " + result);
return;
@@ -3633,14 +4064,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final Float currentScore = scoresByField.get(categoryId);
if (currentScore != null && currentScore > score) {
if (sVerbose) {
- Slog.v(TAG, "skipping score " + score
- + " because it's less than " + currentScore);
+ Slog.v(
+ TAG,
+ "skipping score "
+ + score
+ + " because it's less than "
+ + currentScore);
}
continue;
}
if (sVerbose) {
- Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
- + autofillId);
+ Slog.v(
+ TAG,
+ "adding score "
+ + score
+ + " at index "
+ + j
+ + " and id "
+ + autofillId);
}
scoresByField.put(categoryId, score);
} else if (sVerbose) {
@@ -3666,13 +4107,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
return;
}
- logContextCommitted(detectedFieldIds, detectedFieldClassifications,
- saveDialogNotShowReason, commitReason);
+ logContextCommitted(
+ detectedFieldIds,
+ detectedFieldClassifications,
+ saveDialogNotShowReason,
+ commitReason);
}
/**
- * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN}
- * when necessary.
+ * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} when
+ * necessary.
*
* <p>Note: It is necessary to call logContextCommitted() first before calling this method.
*/
@@ -3689,11 +4133,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@NonNull
public SaveResult showSaveLocked() {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#showSaveLocked() rejected - session: " + id + " destroyed");
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ false,
Event.NO_SAVE_UI_REASON_NONE);
}
mSessionState = STATE_FINISHED;
@@ -3705,12 +4152,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*/
if (mSessionFlags.mScreenHasCredmanField) {
if (sVerbose) {
- Slog.v(TAG, "Call to Session#showSaveLocked() rejected - "
- + "there is credman field in screen");
+ Slog.v(
+ TAG,
+ "Call to Session#showSaveLocked() rejected - "
+ + "there is credman field in screen");
}
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NONE);
}
@@ -3728,7 +4179,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sVerbose) Slog.v(TAG, "showSaveLocked(" + this.id + "): no saveInfo from service");
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NO_SAVE_INFO);
}
@@ -3737,7 +4190,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sDebug) Slog.v(TAG, "showSaveLocked(" + this.id + "): service asked to delay save");
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ false,
Event.NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG);
}
@@ -3774,12 +4229,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Some apps clear the form before navigating to other activities.
// If current value is empty, consider fall back to last cached
// non-empty result first.
- final AutofillValue candidateSaveValue =
- viewState.getCandidateSaveValue();
+ final AutofillValue candidateSaveValue = viewState.getCandidateSaveValue();
if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
if (sVerbose) {
- Slog.v(TAG, "current value is empty, using cached last non-empty "
- + "value instead");
+ Slog.v(
+ TAG,
+ "current value is empty, using cached last non-empty "
+ + "value instead");
}
value = candidateSaveValue;
} else {
@@ -3788,8 +4244,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue initialValue = getValueFromContextsLocked(id);
if (initialValue != null) {
if (sDebug) {
- Slog.d(TAG, "Value of required field " + id + " didn't change; "
- + "using initial value (" + initialValue + ") instead");
+ Slog.d(
+ TAG,
+ "Value of required field "
+ + id
+ + " didn't change; "
+ + "using initial value ("
+ + initialValue
+ + ") instead");
}
value = initialValue;
} else {
@@ -3821,8 +4283,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue initialValue = getValueFromContextsLocked(id);
if (initialValue != null && initialValue.equals(value)) {
if (sDebug) {
- Slog.d(TAG, "id " + id + " is part of dataset but initial value "
- + "didn't change: " + value);
+ Slog.d(
+ TAG,
+ "id "
+ + id
+ + " is part of dataset but initial value "
+ + "didn't change: "
+ + value);
}
changed = false;
} else {
@@ -3833,8 +4300,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (changed) {
if (sDebug) {
- Slog.d(TAG, "found a change on required " + id + ": " + filledValue
- + " => " + value);
+ Slog.d(
+ TAG,
+ "found a change on required "
+ + id
+ + ": "
+ + filledValue
+ + " => "
+ + value);
}
atLeastOneChanged = true;
}
@@ -3844,8 +4317,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillId[] optionalIds = saveInfo.getOptionalIds();
if (sVerbose) {
- Slog.v(TAG, "allRequiredAreNotEmpty: " + allRequiredAreNotEmpty + " hasOptional: "
- + (optionalIds != null));
+ Slog.v(
+ TAG,
+ "allRequiredAreNotEmpty: "
+ + allRequiredAreNotEmpty
+ + " hasOptional: "
+ + (optionalIds != null));
}
int saveDialogNotShowReason;
if (!allRequiredAreNotEmpty) {
@@ -3859,7 +4336,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// - if at least one required id changed but it was not part of a filled dataset, we
// need to check if an optional id is part of a filled datased (in which case we show
// Update instead of Save)
- if (optionalIds!= null && (!atLeastOneChanged || !isUpdate)) {
+ if (optionalIds != null && (!atLeastOneChanged || !isUpdate)) {
// No change on required ids yet, look for changes on optional ids.
for (int i = 0; i < optionalIds.length; i++) {
final AutofillId id = optionalIds[i];
@@ -3879,8 +4356,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
viewState.getCandidateSaveValue();
if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
if (sVerbose) {
- Slog.v(TAG, "current value is empty, using cached last "
- + "non-empty value instead");
+ Slog.v(
+ TAG,
+ "current value is empty, using cached last "
+ + "non-empty value instead");
}
currentValue = candidateSaveValue;
}
@@ -3897,8 +4376,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue filledValue = viewState.getAutofilledValue();
if (value != null && !value.equals(filledValue)) {
if (sDebug) {
- Slog.d(TAG, "found a change on optional " + id + ": " + filledValue
- + " => " + value);
+ Slog.d(
+ TAG,
+ "found a change on optional "
+ + id
+ + ": "
+ + filledValue
+ + " => "
+ + value);
}
if (filledValue != null) {
isUpdate = true;
@@ -3907,12 +4392,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
atLeastOneChanged = true;
}
- } else {
+ } else {
// Update current values cache based on initial value
final AutofillValue initialValue = getValueFromContextsLocked(id);
if (sDebug) {
- Slog.d(TAG, "no current value for " + id + "; initial value is "
- + initialValue);
+ Slog.d(
+ TAG,
+ "no current value for "
+ + id
+ + "; initial value is "
+ + initialValue);
}
if (initialValue != null) {
currentValues.put(id, initialValue);
@@ -3935,17 +4424,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
try {
isValid = validator.isValid(this);
if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
- log.setType(isValid
- ? MetricsEvent.TYPE_SUCCESS
- : MetricsEvent.TYPE_DISMISS);
+ log.setType(
+ isValid ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_DISMISS);
} catch (Exception e) {
Slog.e(TAG, "Not showing save UI because validation failed:", e);
log.setType(MetricsEvent.TYPE_FAILURE);
mMetricsLogger.write(log);
mSaveEventLogger.maybeSetSaveUiNotShownReason(
- NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
+ NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED);
}
@@ -3953,9 +4443,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (!isValid) {
Slog.i(TAG, "not showing save UI because fields failed validation");
mSaveEventLogger.maybeSetSaveUiNotShownReason(
- NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
+ NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED);
}
}
@@ -3964,15 +4456,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// content.
final List<Dataset> datasets = response.getDatasets();
if (datasets != null) {
- datasets_loop: for (int i = 0; i < datasets.size(); i++) {
+ datasets_loop:
+ for (int i = 0; i < datasets.size(); i++) {
final Dataset dataset = datasets.get(i);
final ArrayMap<AutofillId, AutofillValue> datasetValues =
Helper.getFields(dataset);
if (sVerbose) {
- Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i
- + ": " + dataset + "; savableIds=" + savableIds);
+ Slog.v(
+ TAG,
+ "Checking if saved fields match contents of dataset #"
+ + i
+ + ": "
+ + dataset
+ + "; savableIds="
+ + savableIds);
}
- savable_ids_loop: for (int j = 0; j < savableIds.size(); j++) {
+ savable_ids_loop:
+ for (int j = 0; j < savableIds.size(); j++) {
final AutofillId id = savableIds.valueAt(j);
final AutofillValue currentValue = currentValues.get(id);
if (currentValue == null) {
@@ -3984,20 +4484,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillValue datasetValue = datasetValues.get(id);
if (!currentValue.equals(datasetValue)) {
if (sDebug) {
- Slog.d(TAG, "found a dataset change on id " + id + ": from "
- + datasetValue + " to " + currentValue);
+ Slog.d(
+ TAG,
+ "found a dataset change on id "
+ + id
+ + ": from "
+ + datasetValue
+ + " to "
+ + currentValue);
}
continue datasets_loop;
}
if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id);
}
if (sDebug) {
- Slog.d(TAG, "ignoring Save UI because all fields match contents of "
- + "dataset #" + i + ": " + dataset);
+ Slog.d(
+ TAG,
+ "ignoring Save UI because all fields match contents of "
+ + "dataset #"
+ + i
+ + ": "
+ + dataset);
}
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_DATASET_MATCH);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_DATASET_MATCH);
}
}
@@ -4015,13 +4528,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
wtf(null, "showSaveLocked(): no service label or icon");
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE);
mSaveEventLogger.logAndEndEvent();
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ return new SaveResult(
+ /* logSaveShown= */ false,
+ /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NONE);
}
- getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
- mService.getServicePackageName(), saveInfo, this,
- mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode,
- response.getShowSaveDialogIcon(), mSaveEventLogger);
+ getUiForShowing()
+ .showSaveUi(
+ serviceLabel,
+ serviceIcon,
+ mService.getServicePackageName(),
+ saveInfo,
+ this,
+ mComponentName,
+ this,
+ mContext,
+ mPendingSaveUi,
+ isUpdate,
+ mCompatMode,
+ response.getShowSaveDialogIcon(),
+ mSaveEventLogger);
if (client != null) {
try {
client.setSaveUiState(id, true);
@@ -4031,21 +4557,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
mSessionFlags.mShowingSaveUi = true;
if (sDebug) {
- Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
- + id + "!");
+ Slog.d(
+ TAG,
+ "Good news, everyone! All checks passed, show save UI for " + id + "!");
}
- return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false,
+ return new SaveResult(
+ /* logSaveShown= */ true,
+ /* removeSession= */ false,
Event.NO_SAVE_UI_REASON_NONE);
}
}
// Nothing changed...
if (sDebug) {
- Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities."
- + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
- + ", atLeastOneChanged=" + atLeastOneChanged);
+ Slog.d(
+ TAG,
+ "showSaveLocked("
+ + id
+ + "): with no changes, comes no responsibilities."
+ + "allRequiredAreNotNull="
+ + allRequiredAreNotEmpty
+ + ", atLeastOneChanged="
+ + atLeastOneChanged);
}
- return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
- saveDialogNotShowReason);
+ return new SaveResult(
+ /* logSaveShown= */ false, /* removeSession= */ true, saveDialogNotShowReason);
}
private void logSaveShown() {
@@ -4078,25 +4613,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return sanitized;
}
- /**
- * Returns whether the session is currently showing the save UI
- */
+ /** Returns whether the session is currently showing the save UI */
@GuardedBy("mLock")
boolean isSaveUiShowingLocked() {
return mSessionFlags.mShowingSaveUi;
}
- /**
- * Gets the latest non-empty value for the given id in the autofill contexts.
- */
+ /** Gets the latest non-empty value for the given id in the autofill contexts. */
@GuardedBy("mLock")
@Nullable
private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) {
final int numContexts = mContexts.size();
for (int i = numContexts - 1; i >= 0; i--) {
final FillContext context = mContexts.get(i);
- final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
- autofillId);
+ final ViewNode node =
+ Helper.findViewNodeByAutofillId(context.getStructure(), autofillId);
if (node != null) {
return node;
}
@@ -4104,22 +4635,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return null;
}
- /**
- * Gets the latest non-empty value for the given id in the autofill contexts.
- */
+ /** Gets the latest non-empty value for the given id in the autofill contexts. */
@GuardedBy("mLock")
@Nullable
private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
final int numContexts = mContexts.size();
for (int i = numContexts - 1; i >= 0; i--) {
final FillContext context = mContexts.get(i);
- final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
- autofillId);
+ final ViewNode node =
+ Helper.findViewNodeByAutofillId(context.getStructure(), autofillId);
if (node != null) {
final AutofillValue value = node.getAutofillValue();
if (sDebug) {
- Slog.d(TAG, "getValueFromContexts(" + this.id + "/" + autofillId + ") at "
- + i + ": " + value);
+ Slog.d(
+ TAG,
+ "getValueFromContexts("
+ + this.id
+ + "/"
+ + autofillId
+ + ") at "
+ + i
+ + ": "
+ + value);
}
if (value != null && !value.isEmpty()) {
return value;
@@ -4129,17 +4666,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return null;
}
- /**
- * Gets the latest autofill options for the given id in the autofill contexts.
- */
+ /** Gets the latest autofill options for the given id in the autofill contexts. */
@GuardedBy("mLock")
@Nullable
private CharSequence[] getAutofillOptionsFromContextsLocked(@NonNull AutofillId autofillId) {
final int numContexts = mContexts.size();
for (int i = numContexts - 1; i >= 0; i--) {
final FillContext context = mContexts.get(i);
- final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
- autofillId);
+ final ViewNode node =
+ Helper.findViewNodeByAutofillId(context.getStructure(), autofillId);
if (node != null && node.getAutofillOptions() != null) {
return node.getAutofillOptions();
}
@@ -4160,7 +4695,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final FillContext context = mContexts.get(contextNum);
final ViewNode[] nodes =
- context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
+ context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): updating " + context);
@@ -4191,8 +4726,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sanitizedValue != null) {
node.updateAutofillValue(sanitizedValue);
} else if (sDebug) {
- Slog.d(TAG, "updateValuesForSaveLocked(): not updating field " + id
- + " because it failed sanitization");
+ Slog.d(
+ TAG,
+ "updateValuesForSaveLocked(): not updating field "
+ + id
+ + " because it failed sanitization");
}
}
@@ -4200,28 +4738,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
context.getStructure().sanitizeForParceling(false);
if (sVerbose) {
- Slog.v(TAG, "updateValuesForSaveLocked(): dumping structure of " + context
- + " before calling service.save()");
+ Slog.v(
+ TAG,
+ "updateValuesForSaveLocked(): dumping structure of "
+ + context
+ + " before calling service.save()");
context.getStructure().dump(false);
}
}
}
- /**
- * Calls service when user requested save.
- */
+ /** Calls service when user requested save. */
@GuardedBy("mLock")
void callSaveLocked() {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#callSaveLocked() rejected - session: " + id + " destroyed");
mSaveEventLogger.maybeSetIsSaved(false);
mSaveEventLogger.logAndEndEvent();
return;
}
if (mRemoteFillService == null) {
- wtf(null, "callSaveLocked() called without a remote service. "
- + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
+ wtf(
+ null,
+ "callSaveLocked() called without a remote service. "
+ + "mForAugmentedAutofillOnly: %s",
+ mSessionFlags.mAugmentedAutofillOnly);
mSaveEventLogger.maybeSetIsSaved(false);
mSaveEventLogger.logAndEndEvent();
return;
@@ -4241,7 +4784,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Remove pending fill requests as the session is finished.
cancelCurrentRequestLocked();
- final ArrayList<FillContext> contexts = mergePreviousSessionLocked( /* forSave= */ true);
+ final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ true);
FieldClassificationResponse fieldClassificationResponse =
mClassificationState.mLastFieldClassificationResponse;
@@ -4251,8 +4794,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mClientState == null) {
mClientState = new Bundle();
}
- mClientState.putParcelableArrayList(EXTRA_KEY_DETECTIONS, new ArrayList<>(
- fieldClassificationResponse.getClassifications()));
+ mClientState.putParcelableArrayList(
+ EXTRA_KEY_DETECTIONS,
+ new ArrayList<>(fieldClassificationResponse.getClassifications()));
}
final SaveRequest saveRequest =
new SaveRequest(contexts, mClientState, mSelectedDatasetIds);
@@ -4270,11 +4814,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* from previous sessions that were asked by the service to be delayed (if any).
*
* <p>As a side-effect:
+ *
* <ul>
- * <li>If the current {@link #mClientState} is {@code null}, sets it with the last non-
- * {@code null} client state from previous sessions.
+ * <li>If the current {@link #mClientState} is {@code null}, sets it with the last non- {@code
+ * null} client state from previous sessions.
* <li>When {@code forSave} is {@code true}, calls {@link #updateValuesForSaveLocked()} in the
- * previous sessions.
+ * previous sessions.
* </ul>
*/
@NonNull
@@ -4283,30 +4828,51 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ArrayList<FillContext> contexts;
if (previousSessions != null) {
if (sDebug) {
- Slog.d(TAG, "mergeSessions(" + this.id + "): Merging the content of "
- + previousSessions.size() + " sessions for task " + taskId);
+ Slog.d(
+ TAG,
+ "mergeSessions("
+ + this.id
+ + "): Merging the content of "
+ + previousSessions.size()
+ + " sessions for task "
+ + taskId);
}
contexts = new ArrayList<>();
for (int i = 0; i < previousSessions.size(); i++) {
final Session previousSession = previousSessions.get(i);
final ArrayList<FillContext> previousContexts = previousSession.mContexts;
if (previousContexts == null) {
- Slog.w(TAG, "mergeSessions(" + this.id + "): Not merging null contexts from "
- + previousSession.id);
+ Slog.w(
+ TAG,
+ "mergeSessions("
+ + this.id
+ + "): Not merging null contexts from "
+ + previousSession.id);
continue;
}
if (forSave) {
previousSession.updateValuesForSaveLocked();
}
if (sDebug) {
- Slog.d(TAG, "mergeSessions(" + this.id + "): adding " + previousContexts.size()
- + " context from previous session #" + previousSession.id);
+ Slog.d(
+ TAG,
+ "mergeSessions("
+ + this.id
+ + "): adding "
+ + previousContexts.size()
+ + " context from previous session #"
+ + previousSession.id);
}
contexts.addAll(previousContexts);
if (mClientState == null && previousSession.mClientState != null) {
if (sDebug) {
- Slog.d(TAG, "mergeSessions(" + this.id + "): setting client state from "
- + "previous session" + previousSession.id);
+ Slog.d(
+ TAG,
+ "mergeSessions("
+ + this.id
+ + "): setting client state from "
+ + "previous session"
+ + previousSession.id);
}
mClientState = previousSession.mClientState;
}
@@ -4351,8 +4917,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// If it's not, then check if it should start a partition.
if (shouldStartNewPartitionLocked(id, flags)) {
if (sDebug) {
- Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
- + viewState.getStateAsString());
+ Slog.d(
+ TAG,
+ "Starting partition or augmented request for view id "
+ + id
+ + ": "
+ + viewState.getStateAsString());
}
// Fix to always let standard autofill start.
// Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as
@@ -4363,8 +4933,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (sVerbose) {
- Slog.v(TAG, "Not starting new partition for view " + id + ": "
- + viewState.getStateAsString());
+ Slog.v(
+ TAG,
+ "Not starting new partition for view "
+ + id
+ + ": "
+ + viewState.getStateAsString());
}
return Optional.empty();
}
@@ -4373,17 +4947,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* Determines if a new partition should be started for an id.
*
* @param id The id of the view that is entered
- *
* @return {@code true} if a new partition should be started
*/
@GuardedBy("mLock")
private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) {
final ViewState currentView = mViewStates.get(id);
- SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags)
- ? mSecondaryResponses : mResponses;
+ SparseArray<FillResponse> responses =
+ shouldRequestSecondaryProvider(flags) ? mSecondaryResponses : mResponses;
if (responses == null) {
- return currentView != null && (currentView.getState()
- & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0;
+ return currentView != null
+ && (currentView.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST)
+ == 0;
}
if (mSessionFlags.mExpiredResponse) {
@@ -4395,8 +4969,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final int numResponses = responses.size();
if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
- Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
- + " reached maximum of " + AutofillManagerService.getPartitionMaxCount());
+ Slog.e(
+ TAG,
+ "Not starting a new partition on "
+ + id
+ + " because session "
+ + this.id
+ + " reached maximum of "
+ + AutofillManagerService.getPartitionMaxCount());
return false;
}
@@ -4437,8 +5017,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
boolean shouldRequestSecondaryProvider(int flags) {
- if (!mService.isAutofillCredmanIntegrationEnabled()
- || mSecondaryProviderHandler == null) {
+ if (!mService.isAutofillCredmanIntegrationEnabled() || mSecondaryProviderHandler == null) {
return false;
}
if (mIsPrimaryCredential) {
@@ -4452,8 +5031,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// 'Session.this.mLock', which is the same as mLock.
@SuppressWarnings("GuardedBy")
@GuardedBy("mLock")
- void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
- int flags) {
+ void updateLocked(
+ AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags) {
if (mDestroyed) {
Slog.w(TAG, "updateLocked(" + id + "): rejected - session: destroyed");
return;
@@ -4464,7 +5043,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired.");
}
mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists(
- NOT_SHOWN_REASON_VIEW_CHANGED);
+ NOT_SHOWN_REASON_VIEW_CHANGED);
mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED");
return;
}
@@ -4474,23 +5053,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sVerbose) {
Slog.v(
TAG,
- "updateLocked(" + id + "): "
- + "id=" + this.id
- + ", action=" + actionAsString(action)
- + ", flags=" + flags
- + ", mCurrentViewId=" + mCurrentViewId
- + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
- + ", viewState=" + viewState);
+ "updateLocked("
+ + id
+ + "): "
+ + "id="
+ + this.id
+ + ", action="
+ + actionAsString(action)
+ + ", flags="
+ + flags
+ + ", mCurrentViewId="
+ + mCurrentViewId
+ + ", mExpiredResponse="
+ + mSessionFlags.mExpiredResponse
+ + ", viewState="
+ + viewState);
}
if (viewState == null) {
- if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED
+ if (action == ACTION_START_SESSION
+ || action == ACTION_VALUE_CHANGED
|| action == ACTION_VIEW_ENTERED) {
if (sVerbose) Slog.v(TAG, "Creating viewState for " + id);
boolean isIgnored = isIgnoredLocked(id);
- viewState = new ViewState(id, this,
- isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL,
- mIsPrimaryCredential);
+ viewState =
+ new ViewState(
+ id,
+ this,
+ isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL,
+ mIsPrimaryCredential);
mViewStates.put(id, viewState);
// TODO(b/73648631): for optimization purposes, should also ignore if change is
@@ -4521,7 +5112,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionFlags.mScreenHasCredmanField = true;
}
- switch(action) {
+ switch (action) {
case ACTION_START_SESSION:
// View is triggering autofill.
mCurrentViewId = viewState.id;
@@ -4545,14 +5136,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
case ACTION_VALUE_CHANGED:
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
// Must cancel the session if the value of the URL bar changed
- final String currentUrl = mUrlBar == null ? null
- : mUrlBar.getText().toString().trim();
+ final String currentUrl =
+ mUrlBar == null ? null : mUrlBar.getText().toString().trim();
if (currentUrl == null) {
// Validation check - shouldn't happen.
wtf(null, "URL bar value changed, but current value is null");
return;
}
- if (value == null || ! value.isText()) {
+ if (value == null || !value.isText()) {
// Validation check - shouldn't happen.
wtf(null, "URL bar value changed to null or non-text: %s", value);
return;
@@ -4567,8 +5158,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// are finished, as the URL bar changed callback is usually called before
// the virtual views become invisible.
if (sDebug) {
- Slog.d(TAG, "Ignoring change on URL because session will finish when "
- + "views are gone");
+ Slog.d(
+ TAG,
+ "Ignoring change on URL because session will finish when "
+ + "views are gone");
}
return;
}
@@ -4598,8 +5191,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// isSameViewEntered has some limitations, where it isn't considered same view when
// autofill suggestions pop up, user selects, and the focus lands back on the view.
// isSameViewAgain tries to overcome that situation.
- final boolean isSameViewAgain = isSameViewEntered
- || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId);
+ final boolean isSameViewAgain =
+ isSameViewEntered
+ || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId);
if (mCurrentViewId != null) {
mPreviousNonNullEnteredViewId = mCurrentViewId;
}
@@ -4641,7 +5235,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// so this calling logViewEntered will be a nop.
// Calling logViewEntered() twice will only log it once
// TODO(271181979): this is broken for multiple partitions
- mService.logViewEntered(this.id, null);
+ mService.logViewEntered(this.id, null, mCurrentViewId);
}
// If this is the first time view is entered for inline, the last
@@ -4655,8 +5249,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Trigger augmented autofill if applicable
if ((flags & FLAG_MANUAL_REQUEST) == 0) {
// Not a manual request
- if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains(
- id)) {
+ if (mAugmentedAutofillableIds != null
+ && mAugmentedAutofillableIds.contains(id)) {
// Regular autofill handled the view and returned null response, but it
// triggered augmented autofill
if (!isSameViewEntered) {
@@ -4664,16 +5258,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
triggerAugmentedAutofillLocked(flags);
} else {
if (sDebug) {
- Slog.d(TAG, "skip augmented autofill for same view: "
- + "same view entered");
+ Slog.d(
+ TAG,
+ "skip augmented autofill for same view: "
+ + "same view entered");
}
}
return;
} else if (mSessionFlags.mAugmentedAutofillOnly && isSameViewEntered) {
// Regular autofill is disabled.
if (sDebug) {
- Slog.d(TAG, "skip augmented autofill for same view: "
- + "standard autofill disabled.");
+ Slog.d(
+ TAG,
+ "skip augmented autofill for same view: "
+ + "standard autofill disabled.");
}
return;
}
@@ -4717,8 +5315,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// on the IME side if it arrives before the input view is finished on the IME.
mInlineSessionController.resetInlineFillUiLocked();
- if ((viewState.getState() &
- ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) != 0) {
+ if ((viewState.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST)
+ != 0) {
// View was exited before Inline Request sent back, do not set it to
// null yet to let onHandleAssistData finish processing
} else {
@@ -4728,7 +5326,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// It's not necessary that there's no more presentation for this view. It could
// be that the user chose some suggestion, in which case, view exits.
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
- NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
+ NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
}
break;
default:
@@ -4737,8 +5335,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private void logPresentationStatsOnViewEnteredLocked(FillResponse response,
- boolean isCredmanRequested) {
+ private void logPresentationStatsOnViewEnteredLocked(
+ FillResponse response, boolean isCredmanRequested) {
mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
mFieldClassificationIdSnapshot);
@@ -4754,16 +5352,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) {
- if ((viewState.getState()
- & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
+ if ((viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
viewState.resetState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL);
cancelAugmentedAutofillLocked();
}
}
- /**
- * Checks whether a view should be ignored.
- */
+ /** Checks whether a view should be ignored. */
@GuardedBy("mLock")
private boolean isIgnoredLocked(AutofillId id) {
// Always check the latest response only
@@ -4782,22 +5377,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
&& getSaveInfoLocked() != null) {
final int length = viewState.getCurrentValue().getTextValue().length();
if (sDebug) {
- Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
- + length + " chars long");
- }
- final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
+ Slog.d(
+ TAG,
+ "updateLocked("
+ + id
+ + "): resetting value that was "
+ + length
+ + " chars long");
+ }
+ final LogMaker log =
+ newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
mMetricsLogger.write(log);
}
}
@GuardedBy("mLock")
- private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value,
- ViewState viewState, int flags) {
+ private void updateViewStateAndUiOnValueChangedLocked(
+ AutofillId id, AutofillValue value, ViewState viewState, int flags) {
// Cache the last non-empty value for save purpose. Some apps clear the form before
// navigating to other activities.
- if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty())
- && viewState.getCurrentValue() != null && viewState.getCurrentValue().isText()
+ if (mIgnoreViewStateResetToEmpty
+ && (value == null || value.isEmpty())
+ && viewState.getCurrentValue() != null
+ && viewState.getCurrentValue().isText()
&& viewState.getCurrentValue().getTextValue() != null
&& viewState.getCurrentValue().getTextValue().length() > 1) {
if (sVerbose) {
@@ -4875,8 +5478,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* indicate the IME attempting to probe the potentially sensitive content of inline suggestions.
*/
@GuardedBy("mLock")
- private void updateFilteringStateOnValueChangedLocked(@Nullable String newTextValue,
- ViewState viewState) {
+ private void updateFilteringStateOnValueChangedLocked(
+ @Nullable String newTextValue, ViewState viewState) {
if (newTextValue == null) {
// Don't just return here, otherwise the IME can circumvent this logic using non-text
// values.
@@ -4900,19 +5503,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ private void resetImeAnimationState() {
+ synchronized (mLock) {
+ mWaitForImeAnimation = false;
+ mImeAnimationStartTimeMs = DEFAULT_UNASSIGNED_TIME;
+ mImeAnimationFinishTimeMs = DEFAULT_UNASSIGNED_TIME;
+ mLastInputStartTime = DEFAULT_UNASSIGNED_TIME;
+ }
+ }
+
@Override
- public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
- @Nullable AutofillValue value, int flags) {
+ public void onFillReady(
+ @NonNull FillResponse response,
+ @NonNull AutofillId filledId,
+ @Nullable AutofillValue value,
+ int flags) {
synchronized (mLock) {
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
mFieldClassificationIdSnapshot);
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#onFillReady() rejected - session: " + id + " destroyed");
mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED);
mSaveEventLogger.logAndEndEvent();
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
- NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY);
+ NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY);
mPresentationStatsEventLogger.logAndEndEvent("on fill ready");
return;
}
@@ -4941,20 +5557,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillId[] ids = response.getFillDialogTriggerIds();
if (ids != null && ArrayUtils.contains(ids, filledId)) {
- if (requestShowFillDialog(response, filledId, filterText, flags)) {
+ @ShowFillDialogState int fillDialogState =
+ requestShowFillDialog(response, filledId, filterText, flags);
+ if (fillDialogState == SHOW_FILL_DIALOG_YES) {
synchronized (mLock) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
}
- // Just show fill dialog once, so disabled after shown.
+ // Just show fill dialog once per fill request, so disabled after shown.
// Note: Cannot disable before requestShowFillDialog() because the method
- // need to check whether fill dialog enabled.
+ // need to check whether fill dialog is enabled.
setFillDialogDisabled();
+ resetImeAnimationState();
return;
- } else {
+ } else if (fillDialogState == SHOW_FILL_DIALOG_NO) {
+ resetImeAnimationState();
setFillDialogDisabled();
+ } else { // SHOW_FILL_DIALOG_WAIT
+ return;
}
-
}
if (response.supportsInlineSuggestions()) {
@@ -4971,10 +5592,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- getUiForShowing().showFillUi(filledId, response, filterText,
- mService.getServicePackageName(), mComponentName,
- serviceLabel, serviceIcon, this, mContext, id, mCompatMode,
- mService.getMaster().getMaxInputLengthForAutofill());
+ getUiForShowing()
+ .showFillUi(
+ filledId,
+ response,
+ filterText,
+ mService.getServicePackageName(),
+ mComponentName,
+ serviceLabel,
+ serviceIcon,
+ this,
+ mContext,
+ id,
+ mCompatMode,
+ mService.getMaster().getMaxInputLengthForAutofill());
synchronized (mLock) {
if (mUiShownTime == 0) {
@@ -4983,21 +5614,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final long duration = mUiShownTime - mStartTime;
if (sDebug) {
- final StringBuilder msg = new StringBuilder("1st UI for ")
- .append(mActivityToken)
- .append(" shown in ");
+ final StringBuilder msg =
+ new StringBuilder("1st UI for ")
+ .append(mActivityToken)
+ .append(" shown in ");
TimeUtils.formatDuration(duration, msg);
Slog.d(TAG, msg.toString());
}
- final StringBuilder historyLog = new StringBuilder("id=").append(id)
- .append(" app=").append(mActivityToken)
- .append(" svc=").append(mService.getServicePackageName())
- .append(" latency=");
+ final StringBuilder historyLog =
+ new StringBuilder("id=")
+ .append(id)
+ .append(" app=")
+ .append(mActivityToken)
+ .append(" svc=")
+ .append(mService.getServicePackageName())
+ .append(" latency=");
TimeUtils.formatDuration(duration, historyLog);
mUiLatencyHistory.log(historyLog.toString());
- addTaggedDataToRequestLogLocked(response.getRequestId(),
- MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
+ addTaggedDataToRequestLogLocked(
+ response.getRequestId(), MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
}
}
}
@@ -5034,7 +5670,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ private void enableFillDialog() {
+ if (sVerbose) {
+ Slog.v(TAG, "Enabling Fill Dialog....");
+ }
+ synchronized (mLock) {
+ mSessionFlags.mFillDialogDisabled = false;
+ }
+ notifyClientFillDialogTriggerIds(null);
+ }
+
private void setFillDialogDisabled() {
+ if (sVerbose) {
+ Slog.v(TAG, "Disabling Fill Dialog.");
+ }
synchronized (mLock) {
mSessionFlags.mFillDialogDisabled = true;
}
@@ -5052,24 +5701,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- private boolean requestShowFillDialog(FillResponse response,
- AutofillId filledId, String filterText, int flags) {
+ private @ShowFillDialogState int requestShowFillDialog(
+ FillResponse response, AutofillId filledId, String filterText, int flags) {
if (!isFillDialogUiEnabled()) {
+ // TODO(b/377868687): The above check includes credman fields. We may want to show
+ // credman fields again.
// Unsupported fill dialog UI
- if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled");
- return false;
+ if (sDebug) Log.w(TAG, "requestShowFillDialog(): fill dialog is disabled");
+ return SHOW_FILL_DIALOG_NO;
}
- if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
- // IME is showing, fallback to normal suggestions UI
- if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing");
- return false;
- }
+ if (!mImproveFillDialogEnabled) {
+ if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
+ // IME is showing, fallback to normal suggestions UI
+ if (sDebug) Log.w(TAG, "requestShowFillDialog(): IME is showing");
+ return SHOW_FILL_DIALOG_NO;
+ }
- if (mInlineSessionController.isImeShowing()) {
- // IME is showing, fallback to normal suggestions UI
- // Note: only work when inline suggestions supported
- return false;
+ if (mInlineSessionController.isImeShowing()) {
+ // IME is showing, fallback to normal suggestions UI
+ // Note: only work when inline suggestions supported
+ return SHOW_FILL_DIALOG_NO;
+ }
}
synchronized (mLock) {
@@ -5077,31 +5730,95 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|| !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) {
// Last fill dialog triggered ids are changed.
if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
- return false;
+ return SHOW_FILL_DIALOG_NO;
}
+ if (mImproveFillDialogEnabled && mInlineSessionController.isImeShowing()) {
+ long currentTimestampMs = SystemClock.elapsedRealtime();
+ long durationMs = currentTimestampMs - mLastInputStartTime;
+ if (sVerbose) {
+ Log.d(TAG, "IME is showing. Checking for elapsed time ");
+ Log.d(TAG, "IME is showing. Timestamps start: " + mLastInputStartTime
+ + " current: " + currentTimestampMs + " duration: " + durationMs
+ + " mFillDialogTimeoutMs: " + mFillDialogTimeoutMs);
+ }
+
+ // Following situations can arise wrt IME animation.
+ // 1. No animation happening (eg IME already animated). In that case,
+ // mWaitForImeAnimation should be false. This is possible if the IME is already up
+ // on a field, but the user focusses on another field. Under such condition,
+ // since IME has already animated, there won't be another animation. However,
+ // onInputStartInputView is still called.
+ // 2. Animation is still proceeding. We should wait for animation to finish,
+ // and then proceed.
+ // 3. Animation is complete.
+ if (mWaitForImeAnimation) {
+ // we need to wait for animation to happen. We can't return from here yet.
+ // This is the situation #2 described above.
+ Log.d(TAG, "Waiting for ime animation to complete before showing fill dialog");
+ mFillDialogRunnable = createFillDialogEvalRunnable(
+ response, filledId, filterText, flags);
+ return SHOW_FILL_DIALOG_WAIT;
+ }
+
+ // Incorporate situations 1 & 3 discussed above. We calculate the duration from the
+ // max of start input time or the ime finish time
+ long effectiveDuration = currentTimestampMs
+ - Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs);
+ if (effectiveDuration >= mFillDialogTimeoutMs) {
+ Log.d(TAG, "Fill dialog not shown since IME has been up for more time than "
+ + mFillDialogTimeoutMs + "ms");
+ return SHOW_FILL_DIALOG_NO;
+ } else if (effectiveDuration < mFillDialogMinWaitAfterImeAnimationMs) {
+ // we need to wait for some time after animation ends
+ Runnable runnable = createFillDialogEvalRunnable(
+ response, filledId, filterText, flags);
+ mHandler.postDelayed(runnable,
+ mFillDialogMinWaitAfterImeAnimationMs - effectiveDuration);
+ return SHOW_FILL_DIALOG_WAIT;
+ }
+ }
}
+ showFillDialog(response, filledId, filterText);
+ return SHOW_FILL_DIALOG_YES;
+ }
+
+ private Runnable createFillDialogEvalRunnable(
+ @NonNull FillResponse response,
+ @NonNull AutofillId filledId,
+ String filterText,
+ int flags) {
+ return () -> {
+ synchronized (mLock) {
+ AutofillValue value = AutofillValue.forText(filterText);
+ onFillReady(response, filledId, value, flags);
+ }
+ };
+ }
+
+ private void showFillDialog(FillResponse response, AutofillId filledId, String filterText) {
Drawable serviceIcon = null;
+ PresentationStatsEventLogger logger = null;
synchronized (mLock) {
serviceIcon = getServiceIcon(response);
+ logger = mPresentationStatsEventLogger;
}
getUiForShowing().showFillDialog(filledId, response, filterText,
mService.getServicePackageName(), mComponentName, serviceIcon, this,
- id, mCompatMode, mPresentationStatsEventLogger, mLock);
- return true;
+ id, mCompatMode, logger, mLock);
}
/**
- * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able
- * to be fetched, use the default provider icon instead
+ * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able to
+ * be fetched, use the default provider icon instead
*
* @return Drawable of the provider icon, if it was able to be fetched. Null otherwise
*/
@SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
- // actually the same object as mLock.
- // TODO: Expose mService.mLock or redesign instead.
+ // actually the same object as mLock.
+ // TODO: Expose mService.mLock or redesign instead.
@GuardedBy("mLock")
private Drawable getServiceIcon(FillResponse response) {
Drawable serviceIcon = null;
@@ -5130,14 +5847,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * Get the custom label that was passed through FillResponse. If the custom label
- * wasn't able to be fetched, use the default provider icon instead
+ * Get the custom label that was passed through FillResponse. If the custom label wasn't able to
+ * be fetched, use the default provider icon instead
*
* @return Drawable of the provider icon, if it was able to be fetched. Null otherwise
*/
@SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
- // actually the same object as mLock.
- // TODO: Expose mService.mLock or redesign instead.
+ // actually the same object as mLock.
+ // TODO: Expose mService.mLock or redesign instead.
@GuardedBy("mLock")
private CharSequence getServiceLabel(FillResponse response) {
CharSequence serviceLabel = null;
@@ -5167,11 +5884,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return serviceLabel;
}
- /**
- * Returns whether we made a request to show inline suggestions.
- */
- private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
- @Nullable String filterText) {
+ /** Returns whether we made a request to show inline suggestions. */
+ private boolean requestShowInlineSuggestionsLocked(
+ @NonNull FillResponse response, @Nullable String filterText) {
if (mCurrentViewId == null) {
Log.w(TAG, "requestShowInlineSuggestionsLocked(): no view currently focused");
return false;
@@ -5199,89 +5914,122 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
- new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
- filterText, remoteRenderService, userId, id);
- InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
- new InlineFillUi.InlineSuggestionUiCallback() {
- @Override
- public void autofill(@NonNull Dataset dataset, int datasetIndex) {
- fill(response.getRequestId(), datasetIndex, dataset, UI_TYPE_INLINE);
- }
+ new InlineFillUi.InlineFillUiInfo(
+ inlineSuggestionsRequest.get(),
+ focusedId,
+ filterText,
+ remoteRenderService,
+ userId,
+ id);
+ InlineFillUi inlineFillUi =
+ InlineFillUi.forAutofill(
+ inlineFillUiInfo,
+ response,
+ new InlineFillUi.InlineSuggestionUiCallback() {
+ @Override
+ public void autofill(@NonNull Dataset dataset, int datasetIndex) {
+ fill(
+ response.getRequestId(),
+ datasetIndex,
+ dataset,
+ UI_TYPE_INLINE);
+ }
- @Override
- public void authenticate(int requestId, int datasetIndex) {
- Session.this.authenticate(response.getRequestId(), datasetIndex,
- response.getAuthentication(), response.getClientState(),
- UI_TYPE_INLINE);
- }
+ @Override
+ public void authenticate(int requestId, int datasetIndex) {
+ Session.this.authenticate(
+ response.getRequestId(),
+ datasetIndex,
+ response.getAuthentication(),
+ response.getClientState(),
+ UI_TYPE_INLINE);
+ }
- @Override
- public void startIntentSender(@NonNull IntentSender intentSender) {
- Session.this.startIntentSender(intentSender, new Intent());
- }
+ @Override
+ public void startIntentSender(@NonNull IntentSender intentSender) {
+ Session.this.startIntentSender(intentSender, new Intent());
+ }
- @Override
- public void onError() {
- synchronized (mLock) {
- mInlineSessionController.setInlineFillUiLocked(
- InlineFillUi.emptyUi(focusedId));
- }
- }
+ @Override
+ public void onError() {
+ synchronized (mLock) {
+ mInlineSessionController.setInlineFillUiLocked(
+ InlineFillUi.emptyUi(focusedId));
+ }
+ }
- @Override
- public void onInflate() {
- Session.this.onShown(UI_TYPE_INLINE, 1);
- }
- }, mService.getMaster().getMaxInputLengthForAutofill());
+ @Override
+ public void onInflate() {
+ Session.this.onShown(UI_TYPE_INLINE, 1);
+ }
+ },
+ mService.getMaster().getMaxInputLengthForAutofill());
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
private ResultReceiver constructCredentialManagerCallback(int requestId) {
- final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
- final AutofillId mAutofillId = mCurrentViewId;
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
- Slog.d(TAG, "onReceiveResult from Credential Manager "
- + "bottom sheet with mCurrentViewId: " + mAutofillId);
- GetCredentialResponse getCredentialResponse =
- resultData.getParcelable(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
- GetCredentialResponse.class);
-
- if (Flags.autofillCredmanDevIntegration()) {
- sendCredentialManagerResponseToApp(getCredentialResponse,
- /*exception=*/ null, mAutofillId);
- } else {
- Dataset datasetFromCredential = getDatasetFromCredentialResponse(
- getCredentialResponse);
- if (datasetFromCredential != null) {
- autoFill(requestId, /*datasetIndex=*/-1,
- datasetFromCredential, false,
- UI_TYPE_CREDMAN_BOTTOM_SHEET);
+ final ResultReceiver resultReceiver =
+ new ResultReceiver(mHandler) {
+ final AutofillId mAutofillId = mCurrentViewId;
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+ Slog.d(
+ TAG,
+ "onReceiveResult from Credential Manager "
+ + "bottom sheet with mCurrentViewId: "
+ + mAutofillId);
+ GetCredentialResponse getCredentialResponse =
+ resultData.getParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ GetCredentialResponse.class);
+
+ if (Flags.autofillCredmanDevIntegration()) {
+ sendCredentialManagerResponseToApp(
+ getCredentialResponse, /* exception= */ null, mAutofillId);
+ } else {
+ Dataset datasetFromCredential =
+ getDatasetFromCredentialResponse(getCredentialResponse);
+ if (datasetFromCredential != null) {
+ autoFill(
+ requestId,
+ /* datasetIndex= */ -1,
+ datasetFromCredential,
+ false,
+ UI_TYPE_CREDMAN_BOTTOM_SHEET);
+ }
+ }
+ } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+ String[] exception =
+ resultData.getStringArray(
+ CredentialProviderService
+ .EXTRA_GET_CREDENTIAL_EXCEPTION);
+ if (exception != null && exception.length >= 2) {
+ String errType = exception[0];
+ String errMsg = exception[1];
+ Slog.w(
+ TAG,
+ "Credman bottom sheet from pinned "
+ + "entry failed with: + "
+ + errType
+ + " , "
+ + errMsg);
+ sendCredentialManagerResponseToApp(
+ /* response= */ null,
+ new GetCredentialException(errType, errMsg),
+ mAutofillId);
+ }
+ } else {
+ Slog.d(
+ TAG,
+ "Unknown resultCode from credential "
+ + "manager bottom sheet: "
+ + resultCode);
}
}
- } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
- String[] exception = resultData.getStringArray(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
- if (exception != null && exception.length >= 2) {
- String errType = exception[0];
- String errMsg = exception[1];
- Slog.w(TAG, "Credman bottom sheet from pinned "
- + "entry failed with: + " + errType + " , "
- + errMsg);
- sendCredentialManagerResponseToApp(/*response=*/ null,
- new GetCredentialException(errType, errMsg),
- mAutofillId);
- }
- } else {
- Slog.d(TAG, "Unknown resultCode from credential "
- + "manager bottom sheet: " + resultCode);
- }
- }
- };
- ResultReceiver ipcFriendlyResultReceiver =
- toIpcFriendlyResultReceiver(resultReceiver);
+ };
+ ResultReceiver ipcFriendlyResultReceiver = toIpcFriendlyResultReceiver(resultReceiver);
return ipcFriendlyResultReceiver;
}
@@ -5309,8 +6057,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- private void notifyUnavailableToClient(int sessionFinishedState,
- @Nullable ArrayList<AutofillId> autofillableIds) {
+ private void notifyUnavailableToClient(
+ int sessionFinishedState, @Nullable ArrayList<AutofillId> autofillableIds) {
synchronized (mLock) {
if (mCurrentViewId == null) return;
try {
@@ -5374,27 +6122,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (saveInfo.getRequiredIds() != null) {
Collections.addAll(trackedViews, saveInfo.getRequiredIds());
mSaveEventLogger.maybeSetSaveUiShownReason(
- SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE);
+ SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE);
}
if (saveInfo.getOptionalIds() != null) {
Collections.addAll(trackedViews, saveInfo.getOptionalIds());
mSaveEventLogger.maybeSetSaveUiShownReason(
- SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE);
+ SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE);
}
}
if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
- mSaveEventLogger.maybeSetSaveUiShownReason(
- SAVE_UI_SHOWN_REASON_UNKNOWN);
+ mSaveEventLogger.maybeSetSaveUiShownReason(SAVE_UI_SHOWN_REASON_UNKNOWN);
mSaveEventLogger.maybeSetSaveUiNotShownReason(
- NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG);
+ NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG);
saveOnFinish = false;
}
} else {
flags = 0;
- mSaveEventLogger.maybeSetSaveUiNotShownReason(
- NO_SAVE_REASON_NO_SAVE_INFO);
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO);
saveTriggerId = null;
}
@@ -5427,21 +6173,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
try {
if (sVerbose) {
- Slog.v(TAG, "updateTrackedIdsLocked(): trackedViews: " + trackedViews
- + " fillableIds: " + fillableIds + " triggerId: " + saveTriggerId
- + " saveOnFinish:" + saveOnFinish + " flags: " + flags
- + " hasSaveInfo: " + (saveInfo != null));
- }
- mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
- saveOnFinish, toArray(fillableIds), saveTriggerId, hasAuthentication);
+ Slog.v(
+ TAG,
+ "updateTrackedIdsLocked(): trackedViews: "
+ + trackedViews
+ + " fillableIds: "
+ + fillableIds
+ + " triggerId: "
+ + saveTriggerId
+ + " saveOnFinish:"
+ + saveOnFinish
+ + " flags: "
+ + flags
+ + " hasSaveInfo: "
+ + (saveInfo != null));
+ }
+ mClient.setTrackedViews(
+ id,
+ toArray(trackedViews),
+ mSaveOnAllViewsInvisible,
+ saveOnFinish,
+ toArray(fillableIds),
+ saveTriggerId,
+ hasAuthentication);
} catch (RemoteException e) {
Slog.w(TAG, "Cannot set tracked ids", e);
}
}
- /**
- * Sets the state of views that failed to autofill.
- */
+ /** Sets the state of views that failed to autofill. */
@GuardedBy("mLock")
void setAutofillFailureLocked(@NonNull List<AutofillId> ids, boolean isRefill) {
if (sVerbose && !ids.isEmpty()) {
@@ -5464,9 +6224,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids, isRefill);
}
- /**
- * Sets the state of views that failed to autofill.
- */
+ /** Sets the state of views that failed to autofill. */
@GuardedBy("mLock")
void setViewAutofilledLocked(@NonNull AutofillId id) {
if (sVerbose) {
@@ -5478,17 +6236,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mPresentationStatsEventLogger.maybeAddSuccessId(id);
}
- /**
- * Sets the state of views that failed to autofill.
- */
+ /** Sets the state of views that failed to autofill. */
void setNotifyNotExpiringResponseDuringAuth() {
synchronized (mLock) {
mPresentationStatsEventLogger.maybeSetNotifyNotExpiringResponseDuringAuth();
}
}
- /**
- * Sets the state of views that failed to autofill.
- */
+
+ /** Sets the state of views that failed to autofill. */
void setLogViewEnteredIgnoredDuringAuth() {
synchronized (mLock) {
mPresentationStatsEventLogger.notifyViewEnteredIgnoredDuringAuthCount();
@@ -5496,10 +6251,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private void replaceResponseLocked(@NonNull FillResponse oldResponse,
- @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
+ private void replaceResponseLocked(
+ @NonNull FillResponse oldResponse,
+ @NonNull FillResponse newResponse,
+ @Nullable Bundle newClientState) {
// Disassociate view states with the old response
- setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, /* clearResponse= */ true,
+ setViewStatesLocked(
+ oldResponse,
+ ViewState.STATE_INITIAL,
+ /* clearResponse= */ true,
/* isPrimary= */ true);
// Move over the id
newResponse.setRequestId(oldResponse.getRequestId());
@@ -5519,7 +6279,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ArrayList<AutofillId> autofillableIds;
if (context != null) {
final AssistStructure structure = context.getStructure();
- autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */true);
+ autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */ true);
} else {
Slog.w(TAG, "processNullResponseLocked(): no context for req " + requestId);
autofillableIds = null;
@@ -5535,8 +6295,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags);
if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) {
if (sVerbose) {
- Slog.v(TAG, "canceling session " + id + " when service returned null and it cannot "
- + "be augmented. AutofillableIds: " + autofillableIds);
+ Slog.v(
+ TAG,
+ "canceling session "
+ + id
+ + " when service returned null and it cannot "
+ + "be augmented. AutofillableIds: "
+ + autofillableIds);
}
// Nothing to be done, but need to notify client.
notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds);
@@ -5544,15 +6309,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
} else {
if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) {
if (sVerbose) {
- Slog.v(TAG, "keeping session " + id + " when service returned null and "
- + "augmented service is disabled for password fields. "
- + "AutofillableIds: " + autofillableIds);
+ Slog.v(
+ TAG,
+ "keeping session "
+ + id
+ + " when service returned null and "
+ + "augmented service is disabled for password fields. "
+ + "AutofillableIds: "
+ + autofillableIds);
}
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
} else {
if (sVerbose) {
- Slog.v(TAG, "keeping session " + id + " when service returned null but "
- + "it can be augmented. AutofillableIds: " + autofillableIds);
+ Slog.v(
+ TAG,
+ "keeping session "
+ + id
+ + " when service returned null but "
+ + "it can be augmented. AutofillableIds: "
+ + autofillableIds);
}
}
mAugmentedAutofillableIds = autofillableIds;
@@ -5567,8 +6342,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Tries to trigger Augmented Autofill when the standard service could not fulfill a request.
*
- * <p> The request may not have been sent when this method returns as it may be waiting for
- * the inline suggestion request asynchronously.
+ * <p>The request may not have been sent when this method returns as it may be waiting for the
+ * inline suggestion request asynchronously.
*
* @return callback to destroy the autofill UI, or {@code null} if not supported.
*/
@@ -5582,8 +6357,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// Check if Smart Suggestions is supported...
- final @SmartSuggestionMode int supportedModes = mService
- .getSupportedSmartSuggestionModesLocked();
+ final @SmartSuggestionMode int supportedModes =
+ mService.getSupportedSmartSuggestionModesLocked();
if (supportedModes == 0) {
if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no supported modes");
return null;
@@ -5591,8 +6366,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// ...then if the service is set for the user
- final RemoteAugmentedAutofillService remoteService = mService
- .getRemoteAugmentedAutofillServiceLocked();
+ final RemoteAugmentedAutofillService remoteService =
+ mService.getRemoteAugmentedAutofillServiceLocked();
if (remoteService == null) {
if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user");
return null;
@@ -5612,25 +6387,37 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return null;
}
- final boolean isAllowlisted = mService
- .isWhitelistedForAugmentedAutofillLocked(mComponentName);
+ final boolean isAllowlisted =
+ mService.isWhitelistedForAugmentedAutofillLocked(mComponentName);
if (!isAllowlisted) {
if (sVerbose) {
- Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
- + ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
- }
- logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
- mCurrentViewId, isAllowlisted, /* isInline= */ null);
+ Slog.v(
+ TAG,
+ "triggerAugmentedAutofillLocked(): "
+ + ComponentName.flattenToShortString(mComponentName)
+ + " not whitelisted ");
+ }
+ logAugmentedAutofillRequestLocked(
+ mode,
+ remoteService.getComponentName(),
+ mCurrentViewId,
+ isAllowlisted,
+ /* isInline= */ null);
return null;
}
if (sVerbose) {
- Slog.v(TAG, "calling Augmented Autofill Service ("
- + ComponentName.flattenToShortString(remoteService.getComponentName())
- + ") on view " + mCurrentViewId + " using suggestion mode "
- + getSmartSuggestionModeToString(mode)
- + " when server returned null for session " + this.id);
+ Slog.v(
+ TAG,
+ "calling Augmented Autofill Service ("
+ + ComponentName.flattenToShortString(remoteService.getComponentName())
+ + ") on view "
+ + mCurrentViewId
+ + " using suggestion mode "
+ + getSmartSuggestionModeToString(mode)
+ + " when server returned null for session "
+ + this.id);
}
// Log FillRequest for Augmented Autofill.
mFillRequestEventLogger.startLogForNewRequest();
@@ -5648,8 +6435,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mAugmentedRequestsLogs == null) {
mAugmentedRequestsLogs = new ArrayList<>();
}
- final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
- remoteService.getComponentName().getPackageName());
+ final LogMaker log =
+ newLogMaker(
+ MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
+ remoteService.getComponentName().getPackageName());
mAugmentedRequestsLogs.add(log);
final AutofillId focusedId = mCurrentViewId;
@@ -5715,8 +6504,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
synchronized (session.mLock) {
session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
- mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill,
- result);
+ mFocusedId, /* requestConsumer= */ mRequestAugmentedAutofill, result);
}
}
}
@@ -5741,19 +6529,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mIsAllowlisted = isAllowlisted;
mMode = mode;
mCurrentValue = currentValue;
-
}
+
@Override
public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
Session session = mSessionWeakRef.get();
- if (logIfSessionNull(
- session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) {
+ if (logIfSessionNull(session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) {
return;
}
session.onAugmentedAutofillInlineSuggestionAccept(
inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue);
-
}
}
@@ -5770,8 +6556,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public Boolean apply(InlineFillUi inlineFillUi) {
Session session = mSessionWeakRef.get();
- if (logIfSessionNull(
- session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) {
+ if (logIfSessionNull(session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) {
return false;
}
@@ -5801,8 +6586,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
- * If the session is null or has been destroyed, log the error msg, and return true.
- * This is a helper function intended to be called when de-referencing from a weak reference.
+ * If the session is null or has been destroyed, log the error msg, and return true. This is a
+ * helper function intended to be called when de-referencing from a weak reference.
+ *
* @param session
* @param logPrefix
* @return true if the session is null, false otherwise.
@@ -5830,15 +6616,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
synchronized (mLock) {
final RemoteAugmentedAutofillService remoteService =
mService.getRemoteAugmentedAutofillServiceLocked();
- logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
- focussedId, isAllowlisted, inlineSuggestionsRequest != null);
- remoteService.onRequestAutofillLocked(id, mClient,
- taskId, mComponentName, mActivityToken,
- AutofillId.withoutSession(focussedId), currentValue,
+ logAugmentedAutofillRequestLocked(
+ mode,
+ remoteService.getComponentName(),
+ focussedId,
+ isAllowlisted,
+ inlineSuggestionsRequest != null);
+ remoteService.onRequestAutofillLocked(
+ id,
+ mClient,
+ taskId,
+ mComponentName,
+ mActivityToken,
+ AutofillId.withoutSession(focussedId),
+ currentValue,
inlineSuggestionsRequest,
new AugmentedAutofillInlineSuggestionsResponseCallback(this),
new AugmentedAutofillErrorCallback(this),
- mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
+ mService.getRemoteInlineSuggestionRenderServiceLocked(),
+ userId);
}
}
@@ -5847,15 +6643,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
cancelAugmentedAutofillLocked();
// Also cancel augmented in IME
- mInlineSessionController.setInlineFillUiLocked(
- InlineFillUi.emptyUi(mCurrentViewId));
+ mInlineSessionController.setInlineFillUiLocked(InlineFillUi.emptyUi(mCurrentViewId));
}
}
@GuardedBy("mLock")
private void cancelAugmentedAutofillLocked() {
- final RemoteAugmentedAutofillService remoteService = mService
- .getRemoteAugmentedAutofillServiceLocked();
+ final RemoteAugmentedAutofillService remoteService =
+ mService.getRemoteAugmentedAutofillServiceLocked();
if (remoteService == null) {
Slog.w(TAG, "cancelAugmentedAutofillLocked(): no service for user");
return;
@@ -5865,8 +6660,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private void processResponseLocked(@NonNull FillResponse newResponse,
- @Nullable Bundle newClientState, int flags) {
+ private void processResponseLocked(
+ @NonNull FillResponse newResponse, @Nullable Bundle newClientState, int flags) {
// Make sure we are hiding the UI which will be shown
// only if handling the current response requires it.
mUi.hideAll(this);
@@ -5878,9 +6673,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final int requestId = newResponse.getRequestId();
if (sVerbose) {
- Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
- + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
- + ",newClientState=" + newClientState);
+ Slog.v(
+ TAG,
+ "processResponseLocked(): mCurrentViewId="
+ + mCurrentViewId
+ + ",flags="
+ + flags
+ + ", reqId="
+ + requestId
+ + ", resp="
+ + newResponse
+ + ",newClientState="
+ + newClientState);
}
if (mResponses == null) {
@@ -5892,8 +6696,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mResponses.put(requestId, newResponse);
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
- boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean(
- WEBVIEW_REQUESTED_CREDENTIAL_KEY, false);
+ boolean webviewRequestedCredman =
+ newClientState != null
+ && newClientState.getBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false);
List<Dataset> datasetList = newResponse.getDatasets();
mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman);
@@ -5901,7 +6706,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId);
mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList);
- setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false,
+ setViewStatesLocked(
+ newResponse,
+ ViewState.STATE_FILLABLE,
+ /* clearResponse= */ false,
/* isPrimary= */ true);
updateFillDialogTriggerIdsLocked();
updateTrackedIdsLocked();
@@ -5914,12 +6722,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
currentView.maybeCallOnFillReady(flags);
}
- /**
- * Sets the state of all views in the given response.
- */
+ /** Sets the state of all views in the given response. */
@GuardedBy("mLock")
- private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse,
- boolean isPrimary) {
+ private void setViewStatesLocked(
+ FillResponse response, int state, boolean clearResponse, boolean isPrimary) {
final List<Dataset> datasets = response.getDatasets();
if (datasets != null && !datasets.isEmpty()) {
for (int i = 0; i < datasets.size(); i++) {
@@ -5964,12 +6770,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- /**
- * Sets the state and response of all views in the given dataset.
- */
+ /** Sets the state and response of all views in the given dataset. */
@GuardedBy("mLock")
- private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
- int state, boolean clearResponse, boolean isPrimary) {
+ private void setViewStatesLocked(
+ @Nullable FillResponse response,
+ @NonNull Dataset dataset,
+ int state,
+ boolean clearResponse,
+ boolean isPrimary) {
final ArrayList<AutofillId> ids = dataset.getFieldIds();
final ArrayList<AutofillValue> values = dataset.getFieldValues();
for (int j = 0; j < ids.size(); j++) {
@@ -5989,10 +6797,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
- @Nullable AutofillValue value) {
+ private ViewState createOrUpdateViewStateLocked(
+ @NonNull AutofillId id, int state, @Nullable AutofillValue value) {
ViewState viewState = mViewStates.get(id);
- if (viewState != null) {
+ if (viewState != null) {
viewState.setState(state);
} else {
viewState = new ViewState(id, this, state, mIsPrimaryCredential);
@@ -6008,26 +6816,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return viewState;
}
- void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent,
- int uiType) {
+ void autoFill(
+ int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, int uiType) {
if (sDebug) {
- Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
- + "; dataset=" + dataset);
+ Slog.d(
+ TAG,
+ "autoFill(): requestId="
+ + requestId
+ + "; datasetIdx="
+ + datasetIndex
+ + "; dataset="
+ + dataset);
}
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
- + id + " destroyed");
+ Slog.w(TAG, "Call to Session#autoFill() rejected - session: " + id + " destroyed");
return;
}
// Selected dataset id is logged regardless of authentication result.
mPresentationStatsEventLogger.maybeSetSelectedDatasetId(datasetIndex);
mPresentationStatsEventLogger.maybeSetSelectedDatasetPickReason(
- dataset.getEligibleReason());
+ dataset.getEligibleReason());
// Autofill it directly...
if (dataset.getAuthentication() == null) {
if (generateEvent) {
- mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType);
+ mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType,
+ mCurrentViewId);
}
if (mCurrentViewId != null) {
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
@@ -6037,12 +6851,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// ...or handle authentication.
- mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType);
+ mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType,
+ mCurrentViewId);
mPresentationStatsEventLogger.maybeSetAuthenticationType(
- AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
+ AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
// does not matter the value of isPrimary because null response won't be overridden.
- setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH,
- /* clearResponse= */ false, /* isPrimary= */ true);
+ setViewStatesLocked(
+ null,
+ dataset,
+ ViewState.STATE_WAITING_DATASET_AUTH,
+ /* clearResponse= */ false,
+ /* isPrimary= */ true);
final Intent fillInIntent;
if (dataset.getCredentialFillInIntent() != null && Flags.autofillCredmanIntegration()) {
Slog.d(TAG, "Setting credential fill intent");
@@ -6055,11 +6874,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
forceRemoveFromServiceLocked();
return;
}
- final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
- datasetIndex);
- startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent,
- /* authenticateInline= */false);
-
+ final int authenticationId =
+ AutofillManager.makeAuthenticationId(requestId, datasetIndex);
+ startAuthentication(
+ authenticationId,
+ dataset.getAuthentication(),
+ fillInIntent,
+ /* authenticateInline= */ false);
}
}
@@ -6072,13 +6893,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final FillContext context = getFillContextByRequestIdLocked(requestId);
if (context == null) {
- wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s",
- requestId, mContexts);
+ wtf(
+ null,
+ "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s",
+ requestId,
+ mContexts);
return null;
}
if (mLastInlineSuggestionsRequest != null
&& mLastInlineSuggestionsRequest.first == requestId) {
- fillInIntent.putExtra(AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST,
+ fillInIntent.putExtra(
+ AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST,
mLastInlineSuggestionsRequest.second);
}
fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
@@ -6121,12 +6946,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- private void startAuthentication(int authenticationId, IntentSender intent,
- Intent fillInIntent, boolean authenticateInline) {
+ private void startAuthentication(
+ int authenticationId,
+ IntentSender intent,
+ Intent fillInIntent,
+ boolean authenticateInline) {
try {
synchronized (mLock) {
- mClient.authenticate(id, authenticationId, intent, fillInIntent,
- authenticateInline);
+ mClient.authenticate(
+ id, authenticationId, intent, fillInIntent, authenticateInline);
}
} catch (RemoteException e) {
Slog.e(TAG, "Error launching auth intent", e);
@@ -6139,22 +6967,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* @hide
*/
static final class SaveResult {
- /**
- * Whether to record the save dialog has been shown.
- */
+ /** Whether to record the save dialog has been shown. */
private boolean mLogSaveShown;
- /**
- * Whether to remove the session.
- */
+ /** Whether to remove the session. */
private boolean mRemoveSession;
- /**
- * The reason why a save dialog was not shown.
- */
+ /** The reason why a save dialog was not shown. */
@NoSaveReason private int mSaveDialogNotShowReason;
- SaveResult(boolean logSaveShown, boolean removeSession,
+ SaveResult(
+ boolean logSaveShown,
+ boolean removeSession,
@NoSaveReason int saveDialogNotShowReason) {
mLogSaveShown = logSaveShown;
mRemoveSession = removeSession;
@@ -6218,15 +7042,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public String toString() {
- return "SaveResult: [logSaveShown=" + mLogSaveShown
- + ", removeSession=" + mRemoveSession
- + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason + "]";
+ return "SaveResult: [logSaveShown="
+ + mLogSaveShown
+ + ", removeSession="
+ + mRemoveSession
+ + ", saveDialogNotShowReason="
+ + mSaveDialogNotShowReason
+ + "]";
}
}
/**
- * Class maintaining the state of the requests to
- * {@link android.service.assist.classification.FieldClassificationService}.
+ * Class maintaining the state of the requests to {@link
+ * android.service.assist.classification.FieldClassificationService}.
*/
private static final class ClassificationState {
@@ -6234,18 +7062,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* Initial state indicating that the request for classification hasn't been triggered yet.
*/
private static final int STATE_INITIAL = 1;
- /**
- * Assist request has been triggered, but awaiting response.
- */
+
+ /** Assist request has been triggered, but awaiting response. */
private static final int STATE_PENDING_ASSIST_REQUEST = 2;
- /**
- * Classification request has been triggered, but awaiting response.
- */
+
+ /** Classification request has been triggered, but awaiting response. */
private static final int STATE_PENDING_REQUEST = 3;
- /**
- * Classification response has been received.
- */
+
+ /** Classification response has been received. */
private static final int STATE_RESPONSE = 4;
+
/**
* Classification state has been invalidated, and the last response may no longer be valid.
* This could occur due to various reasons like views changing their layouts, becoming
@@ -6254,15 +7080,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*/
private static final int STATE_INVALIDATED = 5;
- @IntDef(prefix = { "STATE_" }, value = {
- STATE_INITIAL,
- STATE_PENDING_ASSIST_REQUEST,
- STATE_PENDING_REQUEST,
- STATE_RESPONSE,
- STATE_INVALIDATED
- })
+ @IntDef(
+ prefix = {"STATE_"},
+ value = {
+ STATE_INITIAL,
+ STATE_PENDING_ASSIST_REQUEST,
+ STATE_PENDING_REQUEST,
+ STATE_RESPONSE,
+ STATE_INVALIDATED
+ })
@Retention(RetentionPolicy.SOURCE)
- @interface ClassificationRequestState{}
+ @interface ClassificationRequestState {}
@GuardedBy("mLock")
private @ClassificationRequestState int mState = STATE_INITIAL;
@@ -6284,8 +7112,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint
- * being applicable to many types. An example of this being new/change password forms,
- * where you need to confirm the passward twice.
+ * being applicable to many types. An example of this being new/change password forms, where
+ * you need to confirm the passward twice.
*/
@GuardedBy("mLock")
private ArrayMap<String, Set<AutofillId>> mHintsToAutofillIdMap;
@@ -6317,8 +7145,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Process the response received.
+ *
* @return true if the response was processed, false otherwise. If there wasn't any
- * response, yet this function was called, it would return false.
+ * response, yet this function was called, it would return false.
*/
@GuardedBy("mLock")
private boolean processResponse() {
@@ -6358,7 +7187,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private static void processDetections(Set<String> detections, AutofillId id,
+ private static void processDetections(
+ Set<String> detections,
+ AutofillId id,
ArrayMap<String, Set<AutofillId>> currentMap) {
for (String detection : detections) {
Set<AutofillId> autofillIds;
@@ -6416,37 +7247,67 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public String toString() {
return "ClassificationState: ["
- + "state=" + stateToString()
- + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest
- + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse
- + ", mClassificationHintsMap=" + mClassificationHintsMap
- + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap
- + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap
- + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap
+ + "state="
+ + stateToString()
+ + ", mPendingFieldClassificationRequest="
+ + mPendingFieldClassificationRequest
+ + ", mLastFieldClassificationResponse="
+ + mLastFieldClassificationResponse
+ + ", mClassificationHintsMap="
+ + mClassificationHintsMap
+ + ", mClassificationGroupHintsMap="
+ + mClassificationGroupHintsMap
+ + ", mHintsToAutofillIdMap="
+ + mHintsToAutofillIdMap
+ + ", mGroupHintsToAutofillIdMap="
+ + mGroupHintsToAutofillIdMap
+ "]";
}
-
}
@Override
public String toString() {
- return "Session: [id=" + id + ", component=" + mComponentName
- + ", state=" + sessionStateAsString(mSessionState) + "]";
+ return "Session: [id="
+ + id
+ + ", component="
+ + mComponentName
+ + ", state="
+ + sessionStateAsString(mSessionState)
+ + "]";
}
@GuardedBy("mLock")
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
- pw.print(prefix); pw.print("id: "); pw.println(id);
- pw.print(prefix); pw.print("uid: "); pw.println(uid);
- pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
- pw.print(prefix); pw.print("flags: "); pw.println(mFlags);
- pw.print(prefix); pw.print("displayId: "); pw.println(mContext.getDisplayId());
- pw.print(prefix); pw.print("state: "); pw.println(sessionStateAsString(mSessionState));
- pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
- pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
- pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
- pw.print(prefix); pw.print("Time to show UI: ");
+ pw.print(prefix);
+ pw.print("id: ");
+ pw.println(id);
+ pw.print(prefix);
+ pw.print("uid: ");
+ pw.println(uid);
+ pw.print(prefix);
+ pw.print("taskId: ");
+ pw.println(taskId);
+ pw.print(prefix);
+ pw.print("flags: ");
+ pw.println(mFlags);
+ pw.print(prefix);
+ pw.print("displayId: ");
+ pw.println(mContext.getDisplayId());
+ pw.print(prefix);
+ pw.print("state: ");
+ pw.println(sessionStateAsString(mSessionState));
+ pw.print(prefix);
+ pw.print("mComponentName: ");
+ pw.println(mComponentName);
+ pw.print(prefix);
+ pw.print("mActivityToken: ");
+ pw.println(mActivityToken);
+ pw.print(prefix);
+ pw.print("mStartTime: ");
+ pw.println(mStartTime);
+ pw.print(prefix);
+ pw.print("Time to show UI: ");
if (mUiShownTime == 0) {
pw.println("N/A");
} else {
@@ -6454,41 +7315,67 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
pw.println();
}
final int requestLogsSizes = mRequestLogs.size();
- pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes);
+ pw.print(prefix);
+ pw.print("mSessionLogs: ");
+ pw.println(requestLogsSizes);
for (int i = 0; i < requestLogsSizes; i++) {
final int requestId = mRequestLogs.keyAt(i);
final LogMaker log = mRequestLogs.valueAt(i);
- pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req=");
- pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println();
+ pw.print(prefix2);
+ pw.print('#');
+ pw.print(i);
+ pw.print(": req=");
+ pw.print(requestId);
+ pw.print(", log=");
+ dumpRequestLog(pw, log);
+ pw.println();
}
- pw.print(prefix); pw.print("mResponses: ");
+ pw.print(prefix);
+ pw.print("mResponses: ");
if (mResponses == null) {
pw.println("null");
} else {
pw.println(mResponses.size());
for (int i = 0; i < mResponses.size(); i++) {
- pw.print(prefix2); pw.print('#'); pw.print(i);
- pw.print(' '); pw.println(mResponses.valueAt(i));
- }
- }
- pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
- pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
- pw.print(prefix); pw.print("mShowingSaveUi: "); pw.println(mSessionFlags.mShowingSaveUi);
- pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi);
+ pw.print(prefix2);
+ pw.print('#');
+ pw.print(i);
+ pw.print(' ');
+ pw.println(mResponses.valueAt(i));
+ }
+ }
+ pw.print(prefix);
+ pw.print("mCurrentViewId: ");
+ pw.println(mCurrentViewId);
+ pw.print(prefix);
+ pw.print("mDestroyed: ");
+ pw.println(mDestroyed);
+ pw.print(prefix);
+ pw.print("mShowingSaveUi: ");
+ pw.println(mSessionFlags.mShowingSaveUi);
+ pw.print(prefix);
+ pw.print("mPendingSaveUi: ");
+ pw.println(mPendingSaveUi);
final int numberViews = mViewStates.size();
- pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
+ pw.print(prefix);
+ pw.print("mViewStates size: ");
+ pw.println(mViewStates.size());
for (int i = 0; i < numberViews; i++) {
- pw.print(prefix); pw.print("ViewState at #"); pw.println(i);
+ pw.print(prefix);
+ pw.print("ViewState at #");
+ pw.println(i);
mViewStates.valueAt(i).dump(prefix2, pw);
}
- pw.print(prefix); pw.print("mContexts: " );
+ pw.print(prefix);
+ pw.print("mContexts: ");
if (mContexts != null) {
int numContexts = mContexts.size();
for (int i = 0; i < numContexts; i++) {
FillContext context = mContexts.get(i);
- pw.print(prefix2); pw.print(context);
+ pw.print(prefix2);
+ pw.print(context);
if (sVerbose) {
pw.println("AssistStructure dumped at logcat)");
@@ -6500,43 +7387,62 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
pw.println("null");
}
- pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
+ pw.print(prefix);
+ pw.print("mHasCallback: ");
+ pw.println(mHasCallback);
if (mClientState != null) {
- pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw
- .println(" bytes");
- }
- pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode);
- pw.print(prefix); pw.print("mUrlBar: ");
+ pw.print(prefix);
+ pw.print("mClientState: ");
+ pw.print(mClientState.getSize());
+ pw.println(" bytes");
+ }
+ pw.print(prefix);
+ pw.print("mCompatMode: ");
+ pw.println(mCompatMode);
+ pw.print(prefix);
+ pw.print("mUrlBar: ");
if (mUrlBar == null) {
pw.println("N/A");
} else {
- pw.print("id="); pw.print(mUrlBar.getAutofillId());
- pw.print(" domain="); pw.print(mUrlBar.getWebDomain());
- pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText());
- }
- pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println(
- mSaveOnAllViewsInvisible);
- pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds);
+ pw.print("id=");
+ pw.print(mUrlBar.getAutofillId());
+ pw.print(" domain=");
+ pw.print(mUrlBar.getWebDomain());
+ pw.print(" text=");
+ Helper.printlnRedactedText(pw, mUrlBar.getText());
+ }
+ pw.print(prefix);
+ pw.print("mSaveOnAllViewsInvisible: ");
+ pw.println(mSaveOnAllViewsInvisible);
+ pw.print(prefix);
+ pw.print("mSelectedDatasetIds: ");
+ pw.println(mSelectedDatasetIds);
if (mSessionFlags.mAugmentedAutofillOnly) {
- pw.print(prefix); pw.println("For Augmented Autofill Only");
+ pw.print(prefix);
+ pw.println("For Augmented Autofill Only");
}
if (mSessionFlags.mFillDialogDisabled) {
- pw.print(prefix); pw.println("Fill Dialog disabled");
+ pw.print(prefix);
+ pw.println("Fill Dialog disabled");
}
if (mLastFillDialogTriggerIds != null) {
- pw.print(prefix); pw.println("Last Fill Dialog trigger ids: ");
+ pw.print(prefix);
+ pw.println("Last Fill Dialog trigger ids: ");
pw.println(mSelectedDatasetIds);
}
if (mAugmentedAutofillDestroyer != null) {
- pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
+ pw.print(prefix);
+ pw.println("has mAugmentedAutofillDestroyer");
}
if (mAugmentedRequestsLogs != null) {
- pw.print(prefix); pw.print("number augmented requests: ");
+ pw.print(prefix);
+ pw.print("number augmented requests: ");
pw.println(mAugmentedRequestsLogs.size());
}
if (mAugmentedAutofillableIds != null) {
- pw.print(prefix); pw.print("mAugmentedAutofillableIds: ");
+ pw.print(prefix);
+ pw.print("mAugmentedAutofillableIds: ");
pw.println(mAugmentedAutofillableIds);
}
if (mRemoteFillService != null) {
@@ -6545,21 +7451,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) {
- pw.print("CAT="); pw.print(log.getCategory());
+ pw.print("CAT=");
+ pw.print(log.getCategory());
pw.print(", TYPE=");
final int type = log.getType();
switch (type) {
- case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break;
- case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break;
- case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break;
- default: pw.print("UNSUPPORTED");
- }
- pw.print('('); pw.print(type); pw.print(')');
- pw.print(", PKG="); pw.print(log.getPackageName());
- pw.print(", SERVICE="); pw.print(log
- .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE));
- pw.print(", ORDINAL="); pw.print(log
- .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL));
+ case MetricsEvent.TYPE_SUCCESS:
+ pw.print("SUCCESS");
+ break;
+ case MetricsEvent.TYPE_FAILURE:
+ pw.print("FAILURE");
+ break;
+ case MetricsEvent.TYPE_CLOSE:
+ pw.print("CLOSE");
+ break;
+ default:
+ pw.print("UNSUPPORTED");
+ }
+ pw.print('(');
+ pw.print(type);
+ pw.print(')');
+ pw.print(", PKG=");
+ pw.print(log.getPackageName());
+ pw.print(", SERVICE=");
+ pw.print(log.getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE));
+ pw.print(", ORDINAL=");
+ pw.print(log.getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL));
dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS);
dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS);
dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION);
@@ -6569,64 +7486,86 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
pw.print(", AUTH_STATUS=");
switch (authStatus) {
case MetricsEvent.AUTOFILL_AUTHENTICATED:
- pw.print("AUTHENTICATED"); break;
+ pw.print("AUTHENTICATED");
+ break;
case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED:
- pw.print("DATASET_AUTHENTICATED"); break;
+ pw.print("DATASET_AUTHENTICATED");
+ break;
case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION:
- pw.print("INVALID_AUTHENTICATION"); break;
+ pw.print("INVALID_AUTHENTICATION");
+ break;
case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION:
- pw.print("INVALID_DATASET_AUTHENTICATION"); break;
- default: pw.print("UNSUPPORTED");
+ pw.print("INVALID_DATASET_AUTHENTICATION");
+ break;
+ default:
+ pw.print("UNSUPPORTED");
}
- pw.print('('); pw.print(authStatus); pw.print(')');
+ pw.print('(');
+ pw.print(authStatus);
+ pw.print(')');
}
- dumpNumericValue(pw, log, "FC_IDS",
- MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS);
- dumpNumericValue(pw, log, "COMPAT_MODE",
- MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE);
+ dumpNumericValue(
+ pw, log, "FC_IDS", MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS);
+ dumpNumericValue(pw, log, "COMPAT_MODE", MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE);
}
- private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log,
- @NonNull String field, int tag) {
+ private static void dumpNumericValue(
+ @NonNull PrintWriter pw, @NonNull LogMaker log, @NonNull String field, int tag) {
final int value = getNumericValue(log, tag);
if (value != 0) {
- pw.print(", "); pw.print(field); pw.print('='); pw.print(value);
+ pw.print(", ");
+ pw.print(field);
+ pw.print('=');
+ pw.print(value);
}
}
- void sendCredentialManagerResponseToApp(@Nullable GetCredentialResponse response,
- @Nullable GetCredentialException exception, @NonNull AutofillId viewId) {
+ void sendCredentialManagerResponseToApp(
+ @Nullable GetCredentialResponse response,
+ @Nullable GetCredentialException exception,
+ @NonNull AutofillId viewId) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#sendCredentialManagerResponseToApp() rejected "
- + "- session: " + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#sendCredentialManagerResponseToApp() rejected "
+ + "- session: "
+ + id
+ + " destroyed");
return;
}
try {
final ViewState viewState = mViewStates.get(viewId);
if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly()
- && viewState != null && viewState.id.getSessionId() != id) {
+ && viewState != null
+ && viewState.id.getSessionId() != id) {
if (sVerbose) {
- Slog.v(TAG, "Skipping sending credential response to view: "
- + viewId + " as it isn't part of the current session: " + id);
+ Slog.v(
+ TAG,
+ "Skipping sending credential response to view: "
+ + viewId
+ + " as it isn't part of the current session: "
+ + id);
}
}
if (exception != null) {
if (viewId.isVirtualInt()) {
- sendResponseToViewNode(viewId, /*response=*/ null, exception);
+ sendResponseToViewNode(viewId, /* response= */ null, exception);
} else {
- mClient.onGetCredentialException(id, viewId, exception.getType(),
- exception.getMessage());
+ mClient.onGetCredentialException(
+ id, viewId, exception.getType(), exception.getMessage());
}
} else if (response != null) {
if (viewId.isVirtualInt()) {
- sendResponseToViewNode(viewId, response, /*exception=*/ null);
+ sendResponseToViewNode(viewId, response, /* exception= */ null);
} else {
mClient.onGetCredentialResponse(id, viewId, response);
}
} else {
- Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
- + "and exception");
+ Slog.w(
+ TAG,
+ "sendCredentialManagerResponseToApp called with null response"
+ + "and exception");
}
} catch (RemoteException e) {
Slog.w(TAG, "Error sending credential response to activity: " + e);
@@ -6635,23 +7574,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response,
- GetCredentialException exception) {
+ private void sendResponseToViewNode(
+ AutofillId viewId, GetCredentialResponse response, GetCredentialException exception) {
ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
Bundle resultData = new Bundle();
if (response != null) {
resultData.putParcelable(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
- response);
- viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
- resultData);
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+ viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, resultData);
} else if (exception != null) {
resultData.putStringArray(
CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
new String[] {exception.getType(), exception.getMessage()});
- viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR,
- resultData);
+ viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, resultData);
}
} else {
Slog.w(TAG, "View node not found after GetCredentialResponse");
@@ -6661,8 +7597,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
void autoFillApp(Dataset dataset) {
synchronized (mLock) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#autoFillApp() rejected - session: " + id + " destroyed");
return;
}
try {
@@ -6671,8 +7608,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final List<AutofillId> ids = new ArrayList<>(entryCount);
final List<AutofillValue> values = new ArrayList<>(entryCount);
boolean waitingDatasetAuth = false;
- boolean hideHighlight = (entryCount == 1
- && dataset.getFieldIds().get(0).equals(mCurrentViewId));
+ boolean hideHighlight =
+ highlightAutofillSingleField()
+ ? false
+ : (entryCount == 1
+ && dataset.getFieldIds().get(0).equals(mCurrentViewId));
// Count how many views are filtered because they are not in current session
int numOfViewsFiltered = 0;
for (int i = 0; i < entryCount; i++) {
@@ -6682,10 +7622,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillId viewId = dataset.getFieldIds().get(i);
final ViewState viewState = mViewStates.get(viewId);
if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly()
- && viewState != null && viewState.id.getSessionId() != id) {
+ && viewState != null
+ && viewState.id.getSessionId() != id) {
if (sVerbose) {
- Slog.v(TAG, "Skipping filling view: " +
- viewId + " as it isn't part of the current session: " + id);
+ Slog.v(
+ TAG,
+ "Skipping filling view: "
+ + viewId
+ + " as it isn't part of the current session: "
+ + id);
}
numOfViewsFiltered += 1;
continue;
@@ -6721,8 +7666,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// does not matter the value of isPrimary because null response won't be
// overridden.
- setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED,
- /* clearResponse= */ false, /* isPrimary= */ true);
+ setViewStatesLocked(
+ null,
+ dataset,
+ ViewState.STATE_AUTOFILLED,
+ /* clearResponse= */ false,
+ /* isPrimary= */ true);
}
} catch (RemoteException e) {
Slog.w(TAG, "Error autofilling activity: " + e);
@@ -6750,7 +7699,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionCommittedEventLogger.maybeSetCommitReason(val);
mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
mSessionCommittedEventLogger.maybeSetSessionDurationMillis(
- SystemClock.elapsedRealtime() - mStartTime);
+ SystemClock.elapsedRealtime() - mStartTime);
mFillRequestEventLogger.logAndEndEvent();
mFillResponseEventLogger.logAndEndEvent();
mPresentationStatsEventLogger.logAndEndEvent("log all events");
@@ -6808,8 +7757,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0
- : mAugmentedRequestsLogs.size();
+ final int totalAugmentedRequests =
+ mAugmentedRequestsLogs == null ? 0 : mAugmentedRequestsLogs.size();
if (totalAugmentedRequests > 0) {
if (sVerbose) {
Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests");
@@ -6820,11 +7769,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
+ final LogMaker log =
+ newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
if (totalAugmentedRequests > 0) {
- log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
- totalAugmentedRequests);
+ log.addTaggedData(
+ MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, totalAugmentedRequests);
}
if (mSessionFlags.mAugmentedAutofillOnly) {
log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_AUGMENTED_ONLY, 1);
@@ -6846,8 +7796,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
void forceRemoveFromServiceIfForAugmentedOnlyLocked() {
if (sVerbose) {
- Slog.v(TAG, "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + this.id + "): "
- + mSessionFlags.mAugmentedAutofillOnly);
+ Slog.v(
+ TAG,
+ "forceRemoveFromServiceIfForAugmentedOnlyLocked("
+ + this.id
+ + "): "
+ + mSessionFlags.mAugmentedAutofillOnly);
}
if (!mSessionFlags.mAugmentedAutofillOnly) return;
@@ -6880,9 +7834,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- /**
- * Thread-safe version of {@link #removeFromServiceLocked()}.
- */
+ /** Thread-safe version of {@link #removeFromServiceLocked()}. */
private void removeFromService() {
synchronized (mLock) {
removeFromServiceLocked();
@@ -6897,8 +7849,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
void removeFromServiceLocked() {
if (sVerbose) Slog.v(TAG, "removeFromServiceLocked(" + this.id + "): " + mPendingSaveUi);
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#removeFromServiceLocked() rejected - session: "
- + id + " destroyed");
+ Slog.w(
+ TAG,
+ "Call to Session#removeFromServiceLocked() rejected - session: "
+ + id
+ + " destroyed");
return;
}
if (isSaveUiPendingLocked()) {
@@ -6917,23 +7872,45 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionState = STATE_REMOVED;
}
+ @GuardedBy("mLock")
+ public void notifyImeAnimationStart(long startTimeMs) {
+ mImeAnimationStartTimeMs = startTimeMs;
+ mWaitForImeAnimation = true;
+ }
+
+ @GuardedBy("mLock")
+ public void notifyImeAnimationEnd(long endTimeMs) {
+ mImeAnimationFinishTimeMs = endTimeMs;
+ // Make sure to use mRunnable with synchronized
+ if (mFillDialogRunnable != null) {
+ if (sVerbose) {
+ Log.v(TAG, "Ime animation ended, starting fill dialog.");
+ }
+ mHandler.postDelayed(mFillDialogRunnable, mFillDialogMinWaitAfterImeAnimationMs);
+ mFillDialogRunnable = null;
+ } else {
+ if (sVerbose) {
+ Log.v(TAG, "Ime animation ended, no runnable present.");
+ }
+ }
+ mWaitForImeAnimation = false;
+ }
+
void onPendingSaveUi(int operation, @NonNull IBinder token) {
getUiForShowing().onPendingSaveUi(operation, token);
}
/**
- * Checks whether this session is hiding the Save UI to handle a custom description link for
- * a specific {@code token} created by
- * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
+ * Checks whether this session is hiding the Save UI to handle a custom description link for a
+ * specific {@code token} created by {@link PendingUi#PendingUi(IBinder, int,
+ * IAutoFillManagerClient)}.
*/
@GuardedBy("mLock")
boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
}
- /**
- * Checks whether this session is hiding the Save UI to handle a custom description link.
- */
+ /** Checks whether this session is hiding the Save UI to handle a custom description link. */
@GuardedBy("mLock")
private boolean isSaveUiPendingLocked() {
return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
@@ -6942,8 +7919,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Return latest response index in mResponses SparseArray.
@GuardedBy("mLock")
private int getLastResponseIndexLocked() {
- if (mResponses == null || mResponses.size() == 0) {
- return -1;
+ if (mResponses == null || mResponses.size() == 0) {
+ return -1;
}
List<Integer> requestIdList = new ArrayList<>();
final int responseCount = mResponses.size();
@@ -6967,15 +7944,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private void logAuthenticationStatusLocked(int requestId, int status) {
- addTaggedDataToRequestLogLocked(requestId,
- MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status);
+ addTaggedDataToRequestLogLocked(
+ requestId, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status);
}
@GuardedBy("mLock")
private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) {
final LogMaker requestLog = mRequestLogs.get(requestId);
if (requestLog == null) {
- Slog.w(TAG,
+ Slog.w(
+ TAG,
"addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId);
return;
}
@@ -6983,20 +7961,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
- private void logAugmentedAutofillRequestLocked(int mode,
- ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
+ private void logAugmentedAutofillRequestLocked(
+ int mode,
+ ComponentName augmentedRemoteServiceName,
+ AutofillId focusedId,
+ boolean isWhitelisted,
Boolean isInline) {
final String historyItem =
- "aug:id=" + id + " u=" + uid + " m=" + mode
- + " a=" + ComponentName.flattenToShortString(mComponentName)
- + " f=" + focusedId
- + " s=" + augmentedRemoteServiceName
- + " w=" + isWhitelisted
- + " i=" + isInline;
+ "aug:id="
+ + id
+ + " u="
+ + uid
+ + " m="
+ + mode
+ + " a="
+ + ComponentName.flattenToShortString(mComponentName)
+ + " f="
+ + focusedId
+ + " s="
+ + augmentedRemoteServiceName
+ + " w="
+ + isWhitelisted
+ + " i="
+ + isInline;
mService.getMaster().logRequestLocked(historyItem);
}
- private void wtf(@Nullable Exception e, String fmt, Object...args) {
+ private void wtf(@Nullable Exception e, String fmt, Object... args) {
final String message = String.format(fmt, args);
synchronized (mLock) {
mWtfHistory.log(message);
@@ -7051,13 +8042,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mClassificationState.updateResponseReceived(response);
}
- public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) {
-
- }
+ public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) {}
- public void onClassificationRequestTimeout(int requestId) {
-
- }
+ public void onClassificationRequestTimeout(int requestId) {}
@Override
public void onServiceDied(@NonNull RemoteFieldClassificationService service) {
@@ -7070,12 +8057,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public void logFieldClassificationEvent(
- long startTime, FieldClassificationResponse response,
+ long startTime,
+ FieldClassificationResponse response,
@FieldClassificationEventLogger.FieldClassificationStatus int status) {
final FieldClassificationEventLogger logger = FieldClassificationEventLogger.createLogger();
logger.startNewLogForRequest();
- logger.maybeSetLatencyMillis(
- SystemClock.elapsedRealtime() - startTime);
+ logger.maybeSetLatencyMillis(SystemClock.elapsedRealtime() - startTime);
logger.maybeSetAppPackageUid(uid);
logger.maybeSetNextFillRequestId(mFillRequestIdSnapshot + 1);
logger.maybeSetRequestId(sIdCounterForPcc.get());
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index ffc80ee7d710..7287bdd8e34f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -409,5 +409,10 @@ public final class InlineFillUi {
* Callback to notify inline ui is hidden.
*/
void notifyInlineUiHidden(@NonNull AutofillId autofillId);
+
+ /**
+ * Callback to notify input method started.
+ */
+ void onInputMethodStartInputView();
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 38a412fa063d..50a26b355537 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -27,6 +27,7 @@ import android.service.autofill.InlinePresentation;
import android.util.Slog;
import com.android.server.LocalServices;
+import com.android.server.autofill.Helper;
import com.android.server.autofill.RemoteInlineSuggestionRenderService;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -83,6 +84,10 @@ final class RemoteInlineSuggestionViewConnector {
*/
public boolean renderSuggestion(int width, int height,
@NonNull IInlineSuggestionUiCallback callback) {
+ if (Helper.sanitizeSlice(mInlinePresentation.getSlice()) == null) {
+ if (sDebug) Slog.d(TAG, "Skipped rendering inline suggestion.");
+ return false;
+ }
if (mRemoteRenderService != null) {
if (sDebug) Slog.d(TAG, "Request to recreate the UI");
mRemoteRenderService.renderSuggestion(callback, mInlinePresentation, width, height,
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index d53f949753d8..b4adad2c8bef 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -60,3 +60,28 @@ flag {
bug: "331749778"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_restricted_mode_changes"
+ namespace: "onboarding"
+ description: "Enables the new framework behavior of not putting apps in restricted mode for "
+ "B&R operations in certain cases."
+ bug: "376661510"
+ is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
+ name: "enable_read_all_external_storage_files"
+ is_exported: true
+ namespace: "onboarding"
+ description: "Enables read all external storage files"
+ bug: "376598575"
+}
+flag {
+ name: "enable_metrics_settings_backup_agents"
+ namespace: "onboarding"
+ description: "Enable SettingsBackupAgent to collect B&R agent metrics."
+ bug: "379861078"
+ is_fixed_read_only: true
+}
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
new file mode 100644
index 000000000000..6ced096e8778
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.BackupManagerService.TAG;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAnnotations;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.backup.internal.LifecycleOperationStorage;
+
+import java.util.Set;
+
+/**
+ * Handles the lifecycle of {@link IBackupAgent}s that the {@link UserBackupManagerService}
+ * communicates with.
+ *
+ * <p>There can only be one agent that's connected to at a time.
+ *
+ * <p>There should be only one instance of this class per {@link UserBackupManagerService}.
+ */
+public class BackupAgentConnectionManager {
+
+ /**
+ * Enables the OS making a decision on whether backup restricted mode should be used for apps
+ * that haven't explicitly opted in or out. See
+ * {@link android.content.pm.PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
+
+ // The thread performing the sequence of queued backups binds to each app's agent
+ // in succession. Bind notifications are asynchronously delivered through the
+ // Activity Manager; use this lock object to signal when a requested binding has
+ // completed.
+ private final Object mAgentConnectLock = new Object();
+ @GuardedBy("mAgentConnectLock")
+ @Nullable
+ private BackupAgentConnection mCurrentConnection;
+
+ private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
+ private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
+
+ private final IActivityManager mActivityManager;
+ private final ActivityManagerInternal mActivityManagerInternal;
+ private final LifecycleOperationStorage mOperationStorage;
+ private final PackageManager mPackageManager;
+ private final UserBackupManagerService mUserBackupManagerService;
+ private final int mUserId;
+ private final String mUserIdMsg;
+
+ BackupAgentConnectionManager(LifecycleOperationStorage operationStorage,
+ PackageManager packageManager, UserBackupManagerService userBackupManagerService,
+ int userId) {
+ mActivityManager = ActivityManager.getService();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mOperationStorage = operationStorage;
+ mPackageManager = packageManager;
+ mUserBackupManagerService = userBackupManagerService;
+ mUserId = userId;
+ mUserIdMsg = "[UserID:" + userId + "] ";
+ }
+
+ private static final class BackupAgentConnection {
+ public final ApplicationInfo appInfo;
+ public final int backupMode;
+ public final boolean inRestrictedMode;
+ public IBackupAgent backupAgent;
+ public boolean connecting = true; // Assume we are trying to connect on creation.
+
+ private BackupAgentConnection(ApplicationInfo appInfo, int backupMode,
+ boolean inRestrictedMode) {
+ this.appInfo = appInfo;
+ this.backupMode = backupMode;
+ this.inRestrictedMode = inRestrictedMode;
+ }
+ }
+
+ /**
+ * Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
+ * {@link #agentConnected(String, IBinder)}) or until this operation times out.
+ *
+ * @param backupMode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
+ */
+ @Nullable
+ public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int backupMode,
+ @BackupAnnotations.BackupDestination int backupDestination) {
+ if (app == null) {
+ Slog.w(TAG, mUserIdMsg + "bindToAgentSynchronous for null app");
+ return null;
+ }
+
+ synchronized (mAgentConnectLock) {
+ boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(backupMode,
+ app.packageName);
+ if (mCurrentConnection != null) {
+ Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
+ + mCurrentConnection.appInfo.packageName);
+ }
+ mCurrentConnection = new BackupAgentConnection(app, backupMode, useRestrictedMode);
+
+ // bindBackupAgent() is an async API. It will kick off the app's process and call
+ // agentConnected() when it receives the agent from the app.
+ boolean startedBindSuccessfully = false;
+ try {
+ startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName,
+ backupMode, mUserId, backupDestination, useRestrictedMode);
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
+
+ if (!startedBindSuccessfully) {
+ Slog.w(TAG, mUserIdMsg + "bind request failed for " + app.packageName);
+ mCurrentConnection = null;
+ } else {
+ Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app.packageName);
+
+ // Wait 10 seconds for the agent and then time out if we still haven't bound to it.
+ long timeoutMark = System.currentTimeMillis() + 10 * 1000;
+ while (mCurrentConnection != null && mCurrentConnection.connecting && (
+ System.currentTimeMillis() < timeoutMark)) {
+ try {
+ mAgentConnectLock.wait(5000);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
+ mCurrentConnection = null;
+ }
+ }
+ }
+
+ if (mCurrentConnection != null) {
+ if (!mCurrentConnection.connecting) {
+ return mCurrentConnection.backupAgent;
+ }
+ // If we are still connecting, we've timed out.
+ Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
+ mCurrentConnection = null;
+ }
+
+ mActivityManagerInternal.clearPendingBackup(mUserId);
+ return null;
+ }
+ }
+
+ /**
+ * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
+ * It will tell the app to destroy the agent.
+ *
+ * <p>If {@code allowKill} is set, this will kill the app's process if the app is in restricted
+ * mode or if it was started for restore and specified {@code android:killAfterRestore} in its
+ * manifest.
+ *
+ * @see #shouldUseRestrictedBackupModeForPackage(int, String)
+ */
+ public void unbindAgent(ApplicationInfo app, boolean allowKill) {
+ if (app == null) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent for null app");
+ return;
+ }
+
+ synchronized (mAgentConnectLock) {
+ // Even if we weren't expecting to be bound to this agent, we should still call
+ // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
+ try {
+ mActivityManager.unbindBackupAgent(app);
+
+ // Evaluate this before potentially setting mCurrentConnection = null.
+ boolean willKill = allowKill && shouldKillAppOnUnbind(app);
+
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
+ Slog.w(TAG,
+ mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
+ + " expected: " + mCurrentConnection.appInfo.packageName);
+ } else {
+ mCurrentConnection = null;
+ }
+
+ if (willKill) {
+ Slog.i(TAG, mUserIdMsg + "Killing agent host process");
+ mActivityManager.killApplicationProcess(app.processName, app.uid);
+ }
+ } catch (RemoteException e) {
+ // Can't happen - activity manager is local
+ }
+ }
+ }
+
+ @GuardedBy("mAgentConnectLock")
+ private boolean shouldKillAppOnUnbind(ApplicationInfo app) {
+ // We don't ask system UID processes to be killed.
+ if (UserHandle.isCore(app.uid)) {
+ return false;
+ }
+
+ // If the app is in restricted mode or if we're not sure if it is because our internal
+ // state is messed up, we need to avoid it being stuck in it.
+ if (mCurrentConnection == null || mCurrentConnection.inRestrictedMode) {
+ return true;
+ }
+
+ // App was doing restore and asked to be killed afterwards.
+ return isBackupModeRestore(mCurrentConnection.backupMode)
+ && (app.flags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0;
+ }
+
+ /**
+ * Callback: a requested backup agent has been instantiated. This should only be called from
+ * the {@link ActivityManager} when it's telling us that an agent is ready after a call to
+ * {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
+ */
+ public void agentConnected(String packageName, IBinder agentBinder) {
+ synchronized (mAgentConnectLock) {
+ if (getCallingUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ + " claiming agent connected");
+ return;
+ }
+
+ Slog.d(TAG, mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "was not expecting connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(packageName)) {
+ Slog.w(TAG, mUserIdMsg + "got agent for unexpected package=" + packageName);
+ } else {
+ mCurrentConnection.backupAgent = IBackupAgent.Stub.asInterface(agentBinder);
+ mCurrentConnection.connecting = false;
+ }
+
+ mAgentConnectLock.notifyAll();
+ }
+ }
+
+ /**
+ * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
+ * to come up in the first place, the agentBinder argument will be {@code null}. This should
+ * only be called from the {@link ActivityManager}.
+ */
+ public void agentDisconnected(String packageName) {
+ synchronized (mAgentConnectLock) {
+ if (getCallingUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ + " claiming agent disconnected");
+ return;
+ }
+
+ Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
+ + " died: cancel current operations");
+
+ // Only abort the current connection if the agent we were expecting or already
+ // connected to has disconnected.
+ if (mCurrentConnection != null && mCurrentConnection.appInfo.packageName.equals(
+ packageName)) {
+ mCurrentConnection = null;
+ }
+
+ // Offload operation cancellation off the main thread as the cancellation callbacks
+ // might call out to BackupTransport. Other operations started on the same package
+ // before the cancellation callback has executed will also be cancelled by the callback.
+ Runnable cancellationRunnable = () -> {
+ // handleCancel() causes the PerformFullTransportBackupTask to go on to
+ // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
+ // that the package being backed up doesn't get stuck in restricted mode until the
+ // backup time-out elapses.
+ for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG,
+ mUserIdMsg + "agentDisconnected: will handleCancel(all) for token:"
+ + Integer.toHexString(token));
+ }
+ mUserBackupManagerService.handleCancel(token, true /* cancelAll */);
+ }
+ };
+ getThreadForCancellation(cancellationRunnable).start();
+
+ mAgentConnectLock.notifyAll();
+ }
+ }
+
+ /**
+ * Marks the given set of packages as packages that should not be put into restricted mode if
+ * they are started for the given {@link BackupAnnotations.OperationType}.
+ */
+ public void setNoRestrictedModePackages(Set<String> packageNames,
+ @BackupAnnotations.OperationType int opType) {
+ if (opType == BackupAnnotations.OperationType.BACKUP) {
+ mBackupNoRestrictedModePackages.clear();
+ mBackupNoRestrictedModePackages.addAll(packageNames);
+ } else if (opType == BackupAnnotations.OperationType.RESTORE) {
+ mRestoreNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.addAll(packageNames);
+ } else {
+ throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
+ }
+ }
+
+ /**
+ * Clears the list of packages that should not be put into restricted mode for either backup or
+ * restore.
+ */
+ public void clearNoRestrictedModePackages() {
+ mBackupNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.clear();
+ }
+
+ /**
+ * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
+ * its value is returned. If it hasn't and it targets an SDK below
+ * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
+ * returns the decision made by the {@link android.app.backup.BackupTransport}.
+ *
+ * <p>When this method is called, we should have already asked the transport and cached its
+ * response in {@link #mBackupNoRestrictedModePackages} or
+ * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
+ * any IPC to the transport.
+ */
+ private boolean shouldUseRestrictedBackupModeForPackage(
+ @BackupAnnotations.OperationType int mode, String packageName) {
+ // Key/Value apps are never put in restricted mode.
+ if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
+ || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
+ return false;
+ }
+
+ if (!Flags.enableRestrictedModeChanges()) {
+ return true;
+ }
+
+ try {
+ PackageManager.Property property = mPackageManager.getPropertyAsUser(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
+ packageName, /* className= */ null, mUserId);
+ if (property.isBoolean()) {
+ // If the package has explicitly specified, we won't ask the transport.
+ return property.getBoolean();
+ } else {
+ Slog.w(TAG,
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + "must be a boolean.");
+ }
+ } catch (NameNotFoundException e) {
+ // This is expected when the package has not defined the property in its manifest.
+ }
+
+ // The package has not specified the property. The behavior depends on the package's
+ // targetSdk.
+ // <36 gets the old behavior of always using restricted mode.
+ if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
+ UserHandle.of(mUserId))) {
+ return true;
+ }
+
+ // Apps targeting >=36 get the behavior decided by the transport.
+ // By this point, we should have asked the transport and cached its decision.
+ if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && mBackupNoRestrictedModePackages.contains(packageName)) || (
+ mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
+ && mRestoreNoRestrictedModePackages.contains(packageName))) {
+ Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isBackupModeRestore(int backupMode) {
+ return backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE
+ || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL;
+ }
+
+ @VisibleForTesting
+ Thread getThreadForCancellation(Runnable operation) {
+ return new Thread(operation, /* operationName */ "agent-disconnected");
+ }
+
+ @VisibleForTesting
+ int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 5f0071d47c89..3f6ede95eaf9 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -658,7 +658,8 @@ public class BackupManagerService extends IBackupManager.Stub {
getServiceForUserIfCallerHasPermission(userId, "agentConnected()");
if (userBackupManagerService != null) {
- userBackupManagerService.agentConnected(packageName, agentBinder);
+ userBackupManagerService.getBackupAgentConnectionManager().agentConnected(packageName,
+ agentBinder);
}
}
@@ -683,7 +684,8 @@ public class BackupManagerService extends IBackupManager.Stub {
getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()");
if (userBackupManagerService != null) {
- userBackupManagerService.agentDisconnected(packageName);
+ userBackupManagerService.getBackupAgentConnectionManager().agentDisconnected(
+ packageName);
}
}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index f5d68362c70a..b343ec8e709b 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -146,7 +146,8 @@ public class KeyValueAdbBackupEngine {
private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
try {
- return mBackupManagerService.bindToAgentSynchronous(targetApp,
+ return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
+ targetApp,
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
BackupAnnotations.BackupDestination.CLOUD);
} catch (SecurityException e) {
@@ -299,7 +300,8 @@ public class KeyValueAdbBackupEngine {
}
private void cleanup() {
- mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+ mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mCurrentPackage.applicationInfo, /* allowKill= */ true);
mBlankStateName.delete();
mNewStateName.delete();
mBackupDataName.delete();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ddccb3731cc1..3025e2eaede0 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -44,7 +44,6 @@ import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.IActivityManager;
-import android.app.IBackupAgent;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations;
@@ -79,7 +78,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
@@ -281,6 +279,9 @@ public class UserBackupManagerService {
private static final int SCHEDULE_FILE_VERSION = 1;
public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
+
+ public static final String TELEPHONY_PROVIDER_PACKAGE = "com.android.providers.telephony";
+
public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
// Pseudoname that we use for the Package Manager metadata "package".
@@ -299,8 +300,6 @@ public class UserBackupManagerService {
// CPU on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
- // Timeout interval for deciding that a bind has taken too long.
- private static final long BIND_TIMEOUT_INTERVAL = 10 * 1000;
// Timeout interval for deciding that a clear-data has taken too long.
private static final long CLEAR_DATA_TIMEOUT_INTERVAL = 30 * 1000;
@@ -315,8 +314,6 @@ public class UserBackupManagerService {
private static final String SERIAL_ID_FILE = "serial_id";
- private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages";
-
private final @UserIdInt int mUserId;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final TransportManager mTransportManager;
@@ -351,17 +348,7 @@ public class UserBackupManagerService {
// locking around the pending-backup management
private final Object mQueueLock = new Object();
-
private final UserBackupPreferences mBackupPreferences;
-
- // The thread performing the sequence of queued backups binds to each app's agent
- // in succession. Bind notifications are asynchronously delivered through the
- // Activity Manager; use this lock object to signal when a requested binding has
- // completed.
- private final Object mAgentConnectLock = new Object();
- private IBackupAgent mConnectedAgent;
- private volatile boolean mConnecting;
-
private volatile boolean mBackupRunning;
private volatile long mLastBackupPass;
@@ -391,6 +378,7 @@ public class UserBackupManagerService {
private ActiveRestoreSession mActiveRestoreSession;
+ private final BackupAgentConnectionManager mBackupAgentConnectionManager;
private final LifecycleOperationStorage mOperationStorage;
private final Random mTokenGenerator = new Random();
@@ -520,17 +508,22 @@ public class UserBackupManagerService {
@VisibleForTesting
UserBackupManagerService(Context context, PackageManager packageManager,
LifecycleOperationStorage operationStorage, TransportManager transportManager,
- BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) {
+ BackupHandler backupHandler, BackupManagerConstants backupManagerConstants,
+ IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) {
mContext = context;
mUserId = 0;
mRegisterTransportsRequestedTime = 0;
mPackageManager = packageManager;
mOperationStorage = operationStorage;
+ mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage,
+ mPackageManager, this, mUserId);
mTransportManager = transportManager;
mFullBackupQueue = new ArrayList<>();
mBackupHandler = backupHandler;
mConstants = backupManagerConstants;
+ mActivityManager = activityManager;
+ mActivityManagerInternal = activityManagerInternal;
mBaseStateDir = null;
mDataDir = null;
@@ -540,13 +533,11 @@ public class UserBackupManagerService {
mRunInitReceiver = null;
mRunInitIntent = null;
mAgentTimeoutParameters = null;
- mActivityManagerInternal = null;
mAlarmManager = null;
mWakelock = null;
mBackupPreferences = null;
mBackupPasswordManager = null;
mPackageManagerBinder = null;
- mActivityManager = null;
mBackupManagerBinder = null;
mScheduledBackupEligibility = null;
}
@@ -579,6 +570,8 @@ public class UserBackupManagerService {
mAgentTimeoutParameters.start();
mOperationStorage = new LifecycleOperationStorage(mUserId);
+ mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage,
+ mPackageManager, this, mUserId);
Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null");
mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread);
@@ -1640,65 +1633,6 @@ public class UserBackupManagerService {
}
}
- /** Fires off a backup agent, blocking until it attaches or times out. */
- @Nullable
- public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
- @BackupDestination int backupDestination) {
- IBackupAgent agent = null;
- synchronized (mAgentConnectLock) {
- mConnecting = true;
- mConnectedAgent = null;
- try {
- if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- backupDestination)) {
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
-
- // success; wait for the agent to arrive
- // only wait 10 seconds for the bind to happen
- long timeoutMark = System.currentTimeMillis() + BIND_TIMEOUT_INTERVAL;
- while (mConnecting && mConnectedAgent == null
- && (System.currentTimeMillis() < timeoutMark)) {
- try {
- mAgentConnectLock.wait(5000);
- } catch (InterruptedException e) {
- // just bail
- Slog.w(TAG, addUserIdToLogMessage(mUserId, "Interrupted: " + e));
- mConnecting = false;
- mConnectedAgent = null;
- }
- }
-
- // if we timed out with no connect, abort and move on
- if (mConnecting) {
- Slog.w(
- TAG,
- addUserIdToLogMessage(mUserId, "Timeout waiting for agent " + app));
- mConnectedAgent = null;
- }
- if (DEBUG) {
- Slog.i(TAG, addUserIdToLogMessage(mUserId, "got agent " + mConnectedAgent));
- }
- agent = mConnectedAgent;
- }
- } catch (RemoteException e) {
- // can't happen - ActivityManager is local
- }
- }
- if (agent == null) {
- mActivityManagerInternal.clearPendingBackup(mUserId);
- }
- return agent;
- }
-
- /** Unbind from a backup agent. */
- public void unbindAgent(ApplicationInfo app) {
- try {
- mActivityManager.unbindBackupAgent(app);
- } catch (RemoteException e) {
- // Can't happen - activity manager is local
- }
- }
-
/**
* Clear an application's data after a failed restore, blocking until the operation completes or
* times out.
@@ -2062,39 +1996,6 @@ public class UserBackupManagerService {
return mOperationStorage.isBackupOperationInProgress();
}
- /** Unbind the backup agent and kill the app if it's a non-system app. */
- public void tearDownAgentAndKill(ApplicationInfo app) {
- if (app == null) {
- // Null means the system package, so just quietly move on. :)
- return;
- }
-
- try {
- // unbind and tidy up even on timeout or failure, just in case
- mActivityManager.unbindBackupAgent(app);
-
- // The agent was running with a stub Application object, so shut it down.
- // !!! We hardcode the confirmation UI's package name here rather than use a
- // manifest flag! TODO something less direct.
- if (!UserHandle.isCore(app.uid)
- && !app.packageName.equals("com.android.backupconfirm")) {
- if (MORE_DEBUG) {
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "Killing agent host process"));
- }
- mActivityManager.killApplicationProcess(app.processName, app.uid);
- } else {
- if (MORE_DEBUG) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId, "Not killing after operation: " + app.processName));
- }
- }
- } catch (RemoteException e) {
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "Lost app trying to shut down"));
- }
- }
-
// ----- Full-data backup scheduling -----
/**
@@ -2471,10 +2372,6 @@ public class UserBackupManagerService {
AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId);
}
- // *****************************
- // NEW UNIFIED RESTORE IMPLEMENTATION
- // *****************************
-
/** Schedule a backup pass for {@code packageName}. */
public void dataChangedImpl(String packageName) {
HashSet<String> targets = dataChangedTargets(packageName);
@@ -3604,40 +3501,6 @@ public class UserBackupManagerService {
}
}
- /**
- * We want to skip backup/restore of certain packages if 'backup_skip_user_facing_packages' is
- * set to true in secure settings. See b/153940088 for details.
- *
- * TODO(b/154822946): Remove this logic in the next release.
- */
- public List<PackageInfo> filterUserFacingPackages(List<PackageInfo> packages) {
- if (!shouldSkipUserFacingData()) {
- return packages;
- }
-
- List<PackageInfo> filteredPackages = new ArrayList<>(packages.size());
- for (PackageInfo packageInfo : packages) {
- if (!shouldSkipPackage(packageInfo.packageName)) {
- filteredPackages.add(packageInfo);
- } else {
- Slog.i(TAG, "Will skip backup/restore for " + packageInfo.packageName);
- }
- }
-
- return filteredPackages;
- }
-
- @VisibleForTesting
- public boolean shouldSkipUserFacingData() {
- return Settings.Secure.getInt(mContext.getContentResolver(), SKIP_USER_FACING_PACKAGES,
- /* def */ 0) != 0;
- }
-
- @VisibleForTesting
- public boolean shouldSkipPackage(String packageName) {
- return WALLPAPER_PACKAGE.equals(packageName);
- }
-
private void updateStateForTransport(String newTransportName) {
// Publish the name change
Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -3791,83 +3654,6 @@ public class UserBackupManagerService {
}
/**
- * Callback: a requested backup agent has been instantiated. This should only be called from the
- * {@link ActivityManager}.
- */
- public void agentConnected(String packageName, IBinder agentBinder) {
- synchronized (mAgentConnectLock) {
- if (Binder.getCallingUid() == Process.SYSTEM_UID) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "agentConnected pkg=" + packageName + " agent=" + agentBinder));
- mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
- mConnecting = false;
- } else {
- Slog.w(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Non-system process uid="
- + Binder.getCallingUid()
- + " claiming agent connected"));
- }
- mAgentConnectLock.notifyAll();
- }
- }
-
- /**
- * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
- * to come up in the first place, the agentBinder argument will be {@code null}. This should
- * only be called from the {@link ActivityManager}.
- */
- public void agentDisconnected(String packageName) {
- synchronized (mAgentConnectLock) {
- if (Binder.getCallingUid() == Process.SYSTEM_UID) {
- mConnectedAgent = null;
- mConnecting = false;
- } else {
- Slog.w(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Non-system process uid="
- + Binder.getCallingUid()
- + " claiming agent disconnected"));
- }
- Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName
- + " died: cancel current operations");
-
- // Offload operation cancellation off the main thread as the cancellation callbacks
- // might call out to BackupTransport. Other operations started on the same package
- // before the cancellation callback has executed will also be cancelled by the callback.
- Runnable cancellationRunnable = () -> {
- // handleCancel() causes the PerformFullTransportBackupTask to go on to
- // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
- // that the package being backed up doesn't get stuck in restricted mode until the
- // backup time-out elapses.
- for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
- if (MORE_DEBUG) {
- Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
- + Integer.toHexString(token));
- }
- handleCancel(token, true /* cancelAll */);
- }
- };
- getThreadForAsyncOperation(/* operationName */ "agent-disconnected",
- cancellationRunnable).start();
-
- mAgentConnectLock.notifyAll();
- }
- }
-
- @VisibleForTesting
- Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
- return new Thread(operation, operationName);
- }
-
- /**
* An application being installed will need a restore pass, then the {@link PackageManager} will
* need to be told when the restore is finished.
*/
@@ -4414,4 +4200,8 @@ public class UserBackupManagerService {
public IBackupManager getBackupManagerBinder() {
return mBackupManagerBinder;
}
+
+ public BackupAgentConnectionManager getBackupAgentConnectionManager() {
+ return mBackupAgentConnectionManager;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 12712063e344..cf617a523bec 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -314,7 +314,7 @@ public class FullBackupEngine {
Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
}
mAgent =
- backupManagerService.bindToAgentSynchronous(
+ backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
mBackupEligibilityRules.getBackupDestination());
}
@@ -323,7 +323,8 @@ public class FullBackupEngine {
private void tearDown() {
if (mPkg != null) {
- backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
+ backupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mPkg.applicationInfo, /* allowKill= */ true);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index dc6709141b25..0ba0d710af38 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -503,7 +503,8 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
Slog.w(TAG, "adb backup cancel of " + target);
}
if (target != null) {
- mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ target.applicationInfo, /* allowKill= */ true);
}
mOperationStorage.removeOperation(mCurrentOpToken);
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index cca166b0939c..990c9416e38d 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -16,6 +16,8 @@
package com.android.server.backup.fullbackup;
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
@@ -34,6 +36,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -269,8 +272,6 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
- mPackages = backupManagerService.filterUserFacingPackages(mPackages);
-
Set<String> packageNames = Sets.newHashSet();
for (PackageInfo pkgInfo : mPackages) {
packageNames.add(pkgInfo.packageName);
@@ -388,6 +389,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
+ // We ask the transport which packages should not be put in restricted mode and cache
+ // the result in UBMS to be used later when the apps are started for backup.
+ setNoRestrictedModePackages(transport, mPackages);
+
// Set up to send data to the transport
final int N = mPackages.size();
int chunkSizeInBytes = 8 * 1024; // 8KB
@@ -589,8 +594,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// from the preflight pass. If we got as far as preflight, we now need
// to tear down the target process.
if (mBackupRunner != null) {
- mUserBackupManagerService.tearDownAgentAndKill(
- currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
}
// ... and continue looping.
} else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
@@ -602,7 +607,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
packageName);
}
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
BackupObserverUtils
@@ -610,7 +616,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
BackupManager.ERROR_AGENT_FAILURE);
Slog.w(TAG, "Application failure for package: " + packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
BackupObserverUtils
@@ -619,7 +626,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
Slog.w(TAG, "Backup cancelled. package=" + packageName +
", cancelAll=" + mCancelAll);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
BackupObserverUtils
@@ -629,7 +637,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
// Abort entire backup pass.
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
return;
} else {
// Success!
@@ -694,6 +703,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mUserBackupManagerService.scheduleNextFullBackupJob(backoff);
}
+ // Clear this to avoid using the memory until reboot.
+ mUserBackupManagerService
+ .getBackupAgentConnectionManager().clearNoRestrictedModePackages();
+
Slog.i(TAG, "Full data backup pass finished.");
mUserBackupManagerService.getWakelock().release();
}
@@ -722,6 +735,22 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
+ private void setNoRestrictedModePackages(BackupTransportClient transport,
+ List<PackageInfo> packages) {
+ try {
+ Set<String> packageNames = new ArraySet<>();
+ for (int i = 0; i < packages.size(); i++) {
+ packageNames.add(packages.get(i).packageName);
+ }
+ packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ BACKUP);
+ mUserBackupManagerService.getBackupAgentConnectionManager().setNoRestrictedModePackages(
+ packageNames, BACKUP);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport");
+ }
+ }
+
// Run the backup and pipe it back to the given socket -- expects to run on
// a standalone thread. The runner owns this half of the pipe, and closes
// it to indicate EOD to the other end.
@@ -978,7 +1007,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mIsCancelled = true;
// Cancel tasks spun off by this task.
mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll);
- mUserBackupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mTarget.applicationInfo, /* allowKill= */ true);
// Free up everyone waiting on this task and its children.
mPreflightLatch.countDown();
mBackupLatch.countDown();
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 3a6e1cafa505..689c43a1cc96 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -741,7 +741,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
final IBackupAgent agent;
try {
agent =
- mBackupManagerService.bindToAgentSynchronous(
+ mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL,
mBackupEligibilityRules.getBackupDestination());
if (agent == null) {
@@ -1302,7 +1302,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
// For PM metadata (for which applicationInfo is null) there is no agent-bound state.
if (mCurrentPackage.applicationInfo != null) {
- mBackupManagerService.unbindAgent(mCurrentPackage.applicationInfo);
+ mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mCurrentPackage.applicationInfo, /* allowKill= */ false);
}
mAgent = null;
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 2d99c96452da..237ffa3bdb21 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -410,11 +410,7 @@ public class FullRestoreEngine extends RestoreEngine {
// All set; now set up the IPC and launch the agent
setUpPipes();
- mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
- FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
- ? ApplicationThreadConstants.BACKUP_MODE_RESTORE
- : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
- mBackupEligibilityRules.getBackupDestination());
+ mAgent = bindToAgent(info);
mAgentPackage = pkg;
} catch (IOException | NameNotFoundException e) {
// fall through to error handling
@@ -731,7 +727,8 @@ public class FullRestoreEngine extends RestoreEngine {
latch.await();
}
- mBackupManagerService.tearDownAgentAndKill(app);
+ mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ app, /* allowKill= */ true);
} catch (RemoteException e) {
Slog.d(TAG, "Lost app trying to shut down");
}
@@ -805,15 +802,12 @@ public class FullRestoreEngine extends RestoreEngine {
return packages.contains(packageName);
}
- void sendOnRestorePackage(String name) {
- if (mObserver != null) {
- try {
- // TODO: use a more user-friendly name string
- mObserver.onRestorePackage(name);
- } catch (RemoteException e) {
- Slog.w(TAG, "full restore observer went away: restorePackage");
- mObserver = null;
- }
- }
+ private IBackupAgent bindToAgent(FileMetadata info) {
+ return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
+ mTargetApp,
+ FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
+ ? ApplicationThreadConstants.BACKUP_MODE_RESTORE
+ : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
+ mBackupEligibilityRules.getBackupDestination());
}
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index e536876f6cc3..ec9d340abe45 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -51,8 +51,8 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Slog;
@@ -315,8 +315,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
}
- mAcceptSet = backupManagerService.filterUserFacingPackages(mAcceptSet);
-
if (MORE_DEBUG) {
Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
for (PackageInfo info : mAcceptSet) {
@@ -482,6 +480,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
return;
}
+ // We ask the transport which packages should not be put in restricted mode and cache
+ // the result in UBMS to be used later when the apps are started for restore.
+ setNoRestrictedModePackages(transport, packages);
+
RestoreDescription desc = transport.nextRestorePackage();
if (desc == null) {
Slog.e(TAG, "No restore metadata available; halting");
@@ -809,7 +811,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Good to go! Set up and bind the agent...
mAgent =
- backupManagerService.bindToAgentSynchronous(
+ backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
mCurrentPackage.applicationInfo,
ApplicationThreadConstants.BACKUP_MODE_RESTORE,
mBackupEligibilityRules.getBackupDestination());
@@ -1358,6 +1360,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Clear any ongoing session timeout.
backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+ // Clear this to avoid using the memory until reboot.
+ backupManagerService.getBackupAgentConnectionManager().clearNoRestrictedModePackages();
+
// If we have a PM token, we must under all circumstances be sure to
// handshake when we've finished.
if (mPmToken > 0) {
@@ -1479,49 +1484,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// If this wasn't the PM pseudopackage, tear down the agent side
if (mCurrentPackage.applicationInfo != null) {
- // unbind and tidy up even on timeout or failure
- try {
- backupManagerService
- .getActivityManager()
- .unbindBackupAgent(mCurrentPackage.applicationInfo);
-
- // The agent was probably running with a stub Application object,
- // which isn't a valid run mode for the main app logic. Shut
- // down the app so that next time it's launched, it gets the
- // usual full initialization. Note that this is only done for
- // full-system restores: when a single app has requested a restore,
- // it is explicitly not killed following that operation.
- //
- // We execute this kill when these conditions hold:
- // 1. it's not a system-uid process,
- // 2. the app did not request its own restore (mTargetPackage == null), and
- // either
- // 3a. the app is a full-data target (TYPE_FULL_STREAM) or
- // b. the app does not state android:killAfterRestore="false" in its manifest
- final int appFlags = mCurrentPackage.applicationInfo.flags;
- final boolean killAfterRestore =
- !UserHandle.isCore(mCurrentPackage.applicationInfo.uid)
- && ((mRestoreDescription.getDataType()
- == RestoreDescription.TYPE_FULL_STREAM)
- || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
- != 0));
-
- if (mTargetPackage == null && killAfterRestore) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Restore complete, killing host process of "
- + mCurrentPackage.applicationInfo.processName);
- }
- backupManagerService
- .getActivityManager()
- .killApplicationProcess(
- mCurrentPackage.applicationInfo.processName,
- mCurrentPackage.applicationInfo.uid);
- }
- } catch (RemoteException e) {
- // can't happen; we run in the same process as the activity manager
- }
+ // If mTargetPackage is not null it means the app requested its own restore, in which
+ // case we won't allow the app to be killed.
+ backupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mCurrentPackage.applicationInfo, /* allowKill= */ mTargetPackage == null);
}
// The caller is responsible for reestablishing the state machine; our
@@ -1819,4 +1785,21 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
return packageInfo;
}
+
+ @VisibleForTesting
+ void setNoRestrictedModePackages(BackupTransportClient transport,
+ PackageInfo[] packages) {
+ try {
+ Set<String> packageNames = new ArraySet<>();
+ for (int i = 0; i < packages.length; i++) {
+ packageNames.add(packages[i].packageName);
+ }
+ packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ RESTORE);
+ backupManagerService.getBackupAgentConnectionManager().setNoRestrictedModePackages(
+ packageNames, RESTORE);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve restricted mode packages from transport");
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index daf3415229ea..373811fef802 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -17,6 +17,7 @@
package com.android.server.backup.transport;
import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
@@ -26,6 +27,7 @@ import android.content.pm.PackageInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
@@ -375,6 +377,26 @@ public class BackupTransportClient {
}
/**
+ * See
+ * {@link IBackupTransport#getPackagesThatShouldNotUseRestrictedMode(List, int, AndroidFuture)}.
+ */
+ public Set<String> getPackagesThatShouldNotUseRestrictedMode(Set<String> packageNames,
+ @BackupAnnotations.OperationType
+ int operationType) throws RemoteException {
+ AndroidFuture<List<String>> resultFuture = mTransportFutures.newFuture();
+ mTransportBinder.getPackagesThatShouldNotUseRestrictedMode(List.copyOf(packageNames),
+ operationType,
+ resultFuture);
+ List<String> resultList = getFutureResult(resultFuture);
+ Set<String> set = new ArraySet<>();
+ if (resultList == null) {
+ return set;
+ }
+ set.addAll(resultList);
+ return set;
+ }
+
+ /**
* Allows the {@link TransportConnection} to notify this client
* if the underlying transport has become unusable. If that happens
* we want to cancel all active futures or callbacks.
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index f24a3c1afc86..508b62cd83f0 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -21,6 +21,7 @@ import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.UserBackupManagerService.TELEPHONY_PROVIDER_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -75,6 +76,12 @@ public class BackupEligibilityRules {
systemPackagesAllowedForProfileUser,
Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));
+ static {
+ if (UserManager.isHeadlessSystemUserMode()) {
+ systemPackagesAllowedForNonSystemUsers.add(TELEPHONY_PROVIDER_PACKAGE);
+ }
+ }
+
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final int mUserId;
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 7a2106bb7753..7c8604f06b10 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -240,9 +240,10 @@ class BackupRestoreProcessor {
boolean matchesMacAddress = Objects.equals(
associationInfo.getDeviceMacAddress(),
restored.getDeviceMacAddress());
- boolean matchesTag = !Flags.associationTag()
- || Objects.equals(associationInfo.getTag(), restored.getTag());
- return matchesMacAddress && matchesTag;
+ boolean matchesDeviceId = Flags.associationTag()
+ && (associationInfo.getDeviceId() != null
+ && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId()));
+ return matchesMacAddress || matchesDeviceId;
};
AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 42f69e9ae02f..418f3a18688b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,16 +31,13 @@ import static android.os.UserHandle.getCallingUserId;
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -58,6 +55,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceId;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -69,31 +67,22 @@ import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.MacAddress;
-import android.net.NetworkPolicyManager;
import android.os.Binder;
-import android.os.Environment;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.flags.Flags;
-import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
@@ -114,35 +103,25 @@ import com.android.server.companion.devicepresence.DevicePresenceProcessor;
import com.android.server.companion.devicepresence.ObservableUuid;
import com.android.server.companion.devicepresence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
-import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
-import java.util.Set;
@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService {
private static final String TAG = "CDM_CompanionDeviceManagerService";
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
-
- private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
- private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
private static final int MAX_CN_LENGTH = 500;
- private final ActivityTaskManagerInternal mAtmInternal;
- private final ActivityManagerInternal mAmInternal;
- private final IAppOpsService mAppOpsManager;
- private final PowerExemptionManager mPowerExemptionManager;
- private final PackageManagerInternal mPackageManagerInternal;
-
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private final ObservableUuidStore mObservableUuidStore;
+
+ private final CompanionExemptionProcessor mCompanionExemptionProcessor;
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -156,12 +135,15 @@ public class CompanionDeviceManagerService extends SystemService {
super(context);
final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
- mAppOpsManager = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
- mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
- mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ final PowerExemptionManager powerExemptionManager = context.getSystemService(
+ PowerExemptionManager.class);
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
+ ActivityTaskManagerInternal.class);
+ final ActivityManagerInternal amInternal = LocalServices.getService(
+ ActivityManagerInternal.class);
+ final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
final UserManager userManager = context.getSystemService(UserManager.class);
final PowerManagerInternal powerManagerInternal = LocalServices.getService(
PowerManagerInternal.class);
@@ -173,25 +155,29 @@ public class CompanionDeviceManagerService extends SystemService {
// Init processors
mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
- mPackageManagerInternal, mAssociationStore);
- mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
+ packageManagerInternal, mAssociationStore);
+ mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
mCompanionAppBinder = new CompanionAppBinder(context);
+ mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
+ powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
+ amInternal, mAssociationStore);
+
mDevicePresenceProcessor = new DevicePresenceProcessor(context,
mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
- powerManagerInternal);
+ powerManagerInternal, mCompanionExemptionProcessor);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
- mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
+ mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
- mPackageManagerInternal, mAssociationStore,
+ packageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
// TODO(b/279663946): move context sync to a dedicated system service
@@ -202,7 +188,6 @@ public class CompanionDeviceManagerService extends SystemService {
public void onStart() {
// Init association stores
mAssociationStore.refreshCache();
- mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
// Init UUID store
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -240,11 +225,8 @@ public class CompanionDeviceManagerService extends SystemService {
if (associations.isEmpty()) return;
- updateAtm(userId, associations);
-
- BackgroundThread.getHandler().sendMessageDelayed(
- obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
- MINUTES.toMillis(10));
+ mCompanionExemptionProcessor.updateAtm(userId, associations);
+ mCompanionExemptionProcessor.updateAutoRevokeExemptions();
}
@Override
@@ -262,9 +244,12 @@ public class CompanionDeviceManagerService extends SystemService {
if (!associationsForPackage.isEmpty()) {
Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
+ packageName + "]. Cleaning up CDM data...");
- }
- for (AssociationInfo association : associationsForPackage) {
- mDisassociationProcessor.disassociate(association.getId());
+
+ for (AssociationInfo association : associationsForPackage) {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
+
+ mCompanionAppBinder.onPackageChanged(userId);
}
// Clear observable UUIDs for the package.
@@ -273,19 +258,16 @@ public class CompanionDeviceManagerService extends SystemService {
for (ObservableUuid uuid : uuidsTobeObserved) {
mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
}
-
- mCompanionAppBinder.onPackagesChanged(userId);
}
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
- final List<AssociationInfo> associationsForPackage =
+ final List<AssociationInfo> associations =
mAssociationStore.getAssociationsByPackage(userId, packageName);
- for (AssociationInfo association : associationsForPackage) {
- updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
- association.getPackageName());
- }
+ if (!associations.isEmpty()) {
+ mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
- mCompanionAppBinder.onPackagesChanged(userId);
+ mCompanionAppBinder.onPackageChanged(userId);
+ }
}
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -337,7 +319,6 @@ public class CompanionDeviceManagerService extends SystemService {
public List<AssociationInfo> getAssociations(String packageName, int userId) {
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"get associations");
-
return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
}
@@ -628,16 +609,25 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void enablePermissionsSync(int associationId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system UID");
+ }
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
}
@Override
public void disablePermissionsSync(int associationId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system UID");
+ }
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
}
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system UID");
+ }
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
}
@@ -676,7 +666,7 @@ public class CompanionDeviceManagerService extends SystemService {
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null);
+ null, null, null, false, null, null, null);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -704,22 +694,24 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void setAssociationTag(int associationId, String tag) {
- mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
+ public void setDeviceId(int associationId, DeviceId deviceId) {
+ mAssociationRequestsProcessor.setDeviceId(associationId, deviceId);
}
- @Override
- public void clearAssociationTag(int associationId) {
- setAssociationTag(associationId, null);
- }
@Override
public byte[] getBackupPayload(int userId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system");
+ }
return mBackupRestoreProcessor.getBackupPayload(userId);
}
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
+ if (getCallingUid() != SYSTEM_UID) {
+ throw new SecurityException("Caller must be system");
+ }
mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
}
@@ -750,130 +742,6 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
- /**
- * Update special access for the association's package
- */
- public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
- final PackageInfo packageInfo =
- getPackageInfo(getContext(), userId, packageName);
-
- Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
- }
-
- private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
- if (packageInfo == null) {
- return;
- }
-
- if (containsEither(packageInfo.requestedPermissions,
- android.Manifest.permission.RUN_IN_BACKGROUND,
- android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
- mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
- } else {
- try {
- mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
- } catch (UnsupportedOperationException e) {
- Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
- + " whitelist. It might due to the package is whitelisted by the system.");
- }
- }
-
- NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
- try {
- if (containsEither(packageInfo.requestedPermissions,
- android.Manifest.permission.USE_DATA_IN_BACKGROUND,
- android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
- networkPolicyManager.addUidPolicy(
- packageInfo.applicationInfo.uid,
- NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
- } else {
- networkPolicyManager.removeUidPolicy(
- packageInfo.applicationInfo.uid,
- NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
- }
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, e.getMessage());
- }
-
- exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
- }
-
- private void exemptFromAutoRevoke(String packageName, int uid) {
- try {
- mAppOpsManager.setMode(
- AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
- uid,
- packageName,
- AppOpsManager.MODE_IGNORED);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
- }
- }
-
- private void updateAtm(int userId, List<AssociationInfo> associations) {
- final Set<Integer> companionAppUids = new ArraySet<>();
- for (AssociationInfo association : associations) {
- final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
- 0, userId);
- if (uid >= 0) {
- companionAppUids.add(uid);
- }
- }
- if (mAtmInternal != null) {
- mAtmInternal.setCompanionAppUids(userId, companionAppUids);
- }
- if (mAmInternal != null) {
- // Make a copy of the set and send it to ActivityManager.
- mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
- }
- }
-
- private void maybeGrantAutoRevokeExemptions() {
- Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
-
- PackageManager pm = getContext().getPackageManager();
- for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
- SharedPreferences pref = getContext().getSharedPreferences(
- new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
- Context.MODE_PRIVATE);
- if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
- continue;
- }
-
- try {
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByUser(userId);
- for (AssociationInfo a : associations) {
- try {
- int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
- exemptFromAutoRevoke(a.getPackageName(), uid);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
- }
- }
- } finally {
- pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
- }
- }
- }
-
- private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
- new AssociationStore.OnChangeListener() {
- @Override
- public void onAssociationChanged(int changeType, AssociationInfo association) {
- Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
- + "], association=[" + association);
-
- final int userId = association.getUserId();
- final List<AssociationInfo> updatedAssociations =
- mAssociationStore.getActiveAssociationsByUser(userId);
-
- updateAtm(userId, updatedAssociations);
- updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
- association.getPackageName());
- }
- };
-
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
@@ -896,10 +764,6 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
- private static <T> boolean containsEither(T[] array, T a, T b) {
- return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
- }
-
private class LocalService implements CompanionDeviceManagerServiceInternal {
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 4fc9d55d3e17..280494544e3b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
- /* callback */ null, /* resultReceiver */ null);
+ deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
}
break;
diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
new file mode 100644
index 000000000000..ea2bc17dafcd
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.NetworkPolicyManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.PowerExemptionManager;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SuppressLint("LongLogTag")
+public class CompanionExemptionProcessor {
+
+ private static final String TAG = "CDM_CompanionExemptionProcessor";
+
+ private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
+ private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
+
+ private final Context mContext;
+ private final PowerExemptionManager mPowerExemptionManager;
+ private final AppOpsManager mAppOpsManager;
+ private final PackageManagerInternal mPackageManager;
+ private final ActivityTaskManagerInternal mAtmInternal;
+ private final ActivityManagerInternal mAmInternal;
+ private final AssociationStore mAssociationStore;
+
+ public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager,
+ AppOpsManager appOpsManager, PackageManagerInternal packageManager,
+ ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal,
+ AssociationStore associationStore) {
+ mContext = context;
+ mPowerExemptionManager = powerExemptionManager;
+ mAppOpsManager = appOpsManager;
+ mPackageManager = packageManager;
+ mAtmInternal = atmInternal;
+ mAmInternal = amInternal;
+ mAssociationStore = associationStore;
+
+ mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() {
+ @Override
+ public void onAssociationChanged(int changeType, AssociationInfo association) {
+ final int userId = association.getUserId();
+ final List<AssociationInfo> updatedAssociations =
+ mAssociationStore.getActiveAssociationsByUser(userId);
+
+ updateAtm(userId, updatedAssociations);
+ }
+ });
+ }
+
+ /**
+ * Update ActivityManager and ActivityTaskManager exemptions
+ */
+ public void updateAtm(int userId, List<AssociationInfo> associations) {
+ final Set<Integer> companionAppUids = new ArraySet<>();
+ for (AssociationInfo association : associations) {
+ int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId);
+ if (uid >= 0) {
+ companionAppUids.add(uid);
+ }
+ }
+ if (mAtmInternal != null) {
+ mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+ }
+ if (mAmInternal != null) {
+ // Make a copy of the set and send it to ActivityManager.
+ mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
+ }
+ }
+
+ /**
+ * Update special access for the association's package
+ */
+ public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) {
+ Slog.i(TAG, "Exempting package [" + packageName + "]...");
+
+ final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName);
+
+ Binder.withCleanCallingIdentity(
+ () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices));
+ }
+
+ @SuppressLint("MissingPermission")
+ private void exemptPackageAsSystem(int userId, PackageInfo packageInfo,
+ boolean hasPresentDevices) {
+ if (packageInfo == null) {
+ return;
+ }
+
+ // If the app has run-in-bg permission and present devices, add it to power saver allowlist.
+ if (containsEither(packageInfo.requestedPermissions,
+ android.Manifest.permission.RUN_IN_BACKGROUND,
+ android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
+ && hasPresentDevices) {
+ mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
+ } else {
+ try {
+ mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
+ } catch (UnsupportedOperationException e) {
+ Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
+ + " allowlist. It might be due to the package being allowlisted by the"
+ + " system.");
+ }
+ }
+
+ // If the app has run-in-bg permission and present device, allow metered network use.
+ NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext);
+ try {
+ if (containsEither(packageInfo.requestedPermissions,
+ android.Manifest.permission.USE_DATA_IN_BACKGROUND,
+ android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
+ && hasPresentDevices) {
+ networkPolicyManager.addUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ } else {
+ networkPolicyManager.removeUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, e.getMessage());
+ }
+
+ updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid,
+ !mAssociationStore.getActiveAssociationsByPackage(userId,
+ packageInfo.packageName).isEmpty());
+ }
+
+ /**
+ * Update auto revoke exemptions.
+ * If the app has any association, exempt it from permission auto revoke.
+ */
+ public void updateAutoRevokeExemptions() {
+ Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
+
+ PackageManager pm = mContext.getPackageManager();
+ for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
+ SharedPreferences pref = mContext.getSharedPreferences(
+ new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
+ Context.MODE_PRIVATE);
+ if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
+ continue;
+ }
+
+ try {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getActiveAssociationsByUser(userId);
+ Set<Pair<String, Integer>> exemptedPackages = new HashSet<>();
+ for (AssociationInfo a : associations) {
+ try {
+ int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+ exemptedPackages.add(new Pair<>(a.getPackageName(), uid));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+ }
+ }
+ for (Pair<String, Integer> exemptedPackage : exemptedPackages) {
+ updateAutoRevokeExemption(exemptedPackage.first, exemptedPackage.second, true);
+ }
+ } finally {
+ pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
+ }
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) {
+ try {
+ mAppOpsManager.setMode(
+ AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+ uid,
+ packageName,
+ hasAssociations ? MODE_IGNORED : MODE_ALLOWED);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e);
+ }
+ }
+
+ private <T> boolean containsEither(T[] array, T a, T b) {
+ return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
+ }
+
+}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 0c54720c53e4..f2d019bde703 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -17,10 +17,12 @@
package com.android.server.companion.association;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -36,6 +38,8 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
+import android.companion.DeviceId;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Environment;
import android.util.AtomicFile;
@@ -51,6 +55,7 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -127,8 +132,11 @@ import java.util.concurrent.ConcurrentMap;
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634389553216"
- * system_data_sync_flags="0"/>
- *
+ * system_data_sync_flags="0"
+ * device_icon="device_icon">
+ * <device_id
+ * custom_device_id="1234"/>
+ * </association>
* <association
* id="3"
* profile="android.app.role.COMPANION_DEVICE_WATCH"
@@ -139,7 +147,11 @@ import java.util.concurrent.ConcurrentMap;
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634641160229"
- * system_data_sync_flags="1"/>
+ * system_data_sync_flags="1"
+ * device_icon="device_icon">
+ * <device_id
+ * custom_device_id="1234"/>
+ * </association>
* </associations>
* </state>
* }</pre>
@@ -156,7 +168,7 @@ public final class AssociationDiskStore {
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
private static final String XML_TAG_ASSOCIATION = "association";
- private static final String XML_TAG_TAG = "tag";
+ private static final String XML_TAG_DEVICE_ID = "device_id";
private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
private static final String XML_ATTR_MAX_ID = "max-id";
@@ -172,6 +184,9 @@ public final class AssociationDiskStore {
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
+ private static final String XML_ATTR_DEVICE_ICON = "device_icon";
+ private static final String XML_ATTR_CUSTOM_DEVICE_ID = "custom_device_id";
+ private static final String XML_ATTR_MAC_ADDRESS_DEVICE_ID = "mac_address_device_id";
private static final String LEGACY_XML_ATTR_DEVICE = "device";
@@ -384,16 +399,16 @@ public final class AssociationDiskStore {
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final String tag = readStringAttribute(parser, XML_TAG_TAG);
final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
- return new AssociationInfo(associationId, userId, appPackage, tag,
+ return new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
- timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, /* deviceIcon */ null,
+ /* deviceId */ null);
}
private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -431,7 +446,6 @@ public final class AssociationDiskStore {
final int associationId = readIntAttribute(parser, XML_ATTR_ID);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final String tag = readStringAttribute(parser, XML_TAG_TAG);
final MacAddress macAddress = stringToMacAddress(
readStringAttribute(parser, XML_ATTR_MAC_ADDRESS));
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
@@ -444,10 +458,28 @@ public final class AssociationDiskStore {
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final int systemDataSyncFlags = readIntAttribute(parser,
XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
+ final Icon deviceIcon = byteArrayToIcon(
+ readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
+ parser.nextTag();
+ final DeviceId deviceId = readDeviceId(parser);
- return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
+ return new AssociationInfo(associationId, userId, appPackage, macAddress, displayName,
profile, null, selfManaged, notify, revoked, pending, timeApproved,
- lastTimeConnected, systemDataSyncFlags);
+ lastTimeConnected, systemDataSyncFlags, deviceIcon, deviceId);
+ }
+
+ private static DeviceId readDeviceId(@NonNull TypedXmlPullParser parser)
+ throws XmlPullParserException {
+ if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
+ final String customDeviceId = readStringAttribute(
+ parser, XML_ATTR_CUSTOM_DEVICE_ID);
+ final MacAddress macAddress = stringToMacAddress(
+ readStringAttribute(parser, XML_ATTR_MAC_ADDRESS_DEVICE_ID));
+
+ return new DeviceId(customDeviceId, macAddress);
+ }
+
+ return null;
}
private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -468,7 +500,6 @@ public final class AssociationDiskStore {
writeIntAttribute(serializer, XML_ATTR_ID, a.getId());
writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
- writeStringAttribute(serializer, XML_TAG_TAG, a.getTag());
writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString());
writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName());
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
@@ -480,18 +511,59 @@ public final class AssociationDiskStore {
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
writeIntAttribute(serializer, XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, a.getSystemDataSyncFlags());
+ writeByteArrayAttribute(
+ serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));
+ writeDeviceId(serializer, a);
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
+ private static void writeDeviceId(XmlSerializer parent, @NonNull AssociationInfo a)
+ throws IOException {
+ if (a.getDeviceId() != null) {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
+ writeStringAttribute(
+ serializer,
+ XML_ATTR_CUSTOM_DEVICE_ID,
+ a.getDeviceId().getCustomId()
+ );
+ writeStringAttribute(
+ serializer,
+ XML_ATTR_MAC_ADDRESS_DEVICE_ID,
+ a.getDeviceId().getMacAddressAsString()
+ );
+ serializer.endTag(null, XML_TAG_DEVICE_ID);
+ }
+ }
+
private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
throws XmlPullParserException {
if (isStartOfTag(parser, tag)) return;
throw new XmlPullParserException(
- "Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag");
+ "Should be at the start of \"" + tag + "\" tag");
}
private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
return address != null ? MacAddress.fromString(address) : null;
}
+
+ private static byte[] iconToByteArray(Icon deviceIcon)
+ throws IOException {
+ if (deviceIcon == null) {
+ return null;
+ }
+
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ deviceIcon.writeToStream(byteStream);
+ return byteStream.toByteArray();
+ }
+
+ private static Icon byteArrayToIcon(byte[] bytes) throws IOException {
+ if (bytes == null) {
+ return null;
+ }
+
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+ return Icon.createFromStream(byteStream);
+ }
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index d56f17bf5edd..899b302316f9 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -41,6 +41,7 @@ import android.app.PendingIntent;
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceId;
import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
@@ -48,6 +49,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManagerInternal;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Bundle;
@@ -281,7 +283,7 @@ public class AssociationRequestsProcessor {
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
request.isSelfManaged(),
- callback, resultReceiver);
+ callback, resultReceiver, request.getDeviceIcon());
});
}
@@ -292,15 +294,15 @@ public class AssociationRequestsProcessor {
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
- @Nullable ResultReceiver resultReceiver) {
+ @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
+ macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
- /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
-
+ /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
+ deviceIcon, /* deviceId */ null);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
}
@@ -353,14 +355,14 @@ public class AssociationRequestsProcessor {
}
/**
- * Set association tag.
+ * Set Device id for the association.
*/
- public void setAssociationTag(int associationId, String tag) {
- Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]...");
+ public void setDeviceId(int associationId, DeviceId deviceId) {
+ Slog.i(TAG, "Setting DeviceId=[" + deviceId + "] to id=[" + associationId + "]...");
AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ association = (new AssociationInfo.Builder(association)).setDeviceId(deviceId).build();
mAssociationStore.updateAssociation(association);
}
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 6f0baef019b3..150e8da5f614 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -16,7 +16,7 @@
package com.android.server.companion.association;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static com.android.internal.util.CollectionUtils.any;
@@ -107,7 +107,7 @@ public class DisassociationProcessor {
it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
- if (packageProcessImportance <= IMPORTANCE_VISIBLE && deviceProfile != null
+ if (packageProcessImportance <= IMPORTANCE_FOREGROUND && deviceProfile != null
&& !isRoleInUseByOtherAssociations) {
// Need to remove the app from the list of role holders, but the process is visible
// to the user at the moment, so we'll need to do it later.
@@ -238,12 +238,16 @@ public class DisassociationProcessor {
*/
private class OnPackageVisibilityChangeListener implements
ActivityManager.OnUidImportanceListener {
-
+ // This method is called when the importance of a uid (app) changes.
+ // We only care about changes where the app is moving to the background.
+ // (e.g., the app currently is not at the top of the screen that the user
+ // is interacting with.)
@Override
public void onUidImportance(int uid, int importance) {
- if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
- // The lower the importance value the more "important" the process is.
- // We are only interested when the process ceases to be visible.
+ // Higher importance values indicate the app is less important.
+ // We are only interested when the process importance level
+ // is greater than IMPORTANCE_FOREGROUND.
+ if (importance <= IMPORTANCE_FOREGROUND) {
return;
}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index 60f46887fa5c..8307da5f7218 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -95,7 +95,9 @@ public class CompanionAppBinder {
/**
* On package changed.
*/
- public void onPackagesChanged(@UserIdInt int userId) {
+ public void onPackageChanged(@UserIdInt int userId) {
+ // Note: To invalidate the user space for simplicity. We could alternatively manage each
+ // package, but that would easily cause errors if one case is mis-handled.
mCompanionServicesRegister.invalidate(userId);
}
@@ -299,12 +301,14 @@ public class CompanionAppBinder {
private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
@Override
- public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+ @NonNull
+ public synchronized Map<String, List<ComponentName>> forUser(
@UserIdInt int userId) {
return super.forUser(userId);
}
- synchronized @NonNull List<ComponentName> forPackage(
+ @NonNull
+ synchronized List<ComponentName> forPackage(
@UserIdInt int userId, @NonNull String packageName) {
return forUser(userId).getOrDefault(packageName, Collections.emptyList());
}
@@ -314,7 +318,8 @@ public class CompanionAppBinder {
}
@Override
- protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+ @NonNull
+ protected final Map<String, List<ComponentName>> create(@UserIdInt int userId) {
return PackageUtils.getCompanionServicesForUser(mContext, userId);
}
}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index a374d279af0b..7b4dd7df8be3 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -57,6 +57,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.CompanionExemptionProcessor;
import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
@@ -101,6 +102,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
private final PowerManagerInternal mPowerManagerInternal;
@NonNull
private final UserManager mUserManager;
+ @NonNull
+ private final CompanionExemptionProcessor mCompanionExemptionProcessor;
// NOTE: Same association may appear in more than one of the following sets at the same time.
// (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -111,7 +114,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@NonNull
private final Set<Integer> mNearbyBleDevices = new HashSet<>();
@NonNull
- private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>();
@NonNull
private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
@NonNull
@@ -146,7 +149,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@NonNull UserManager userManager,
@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore,
- @NonNull PowerManagerInternal powerManagerInternal) {
+ @NonNull PowerManagerInternal powerManagerInternal,
+ @NonNull CompanionExemptionProcessor companionExemptionProcessor) {
mContext = context;
mCompanionAppBinder = companionAppBinder;
mAssociationStore = associationStore;
@@ -156,6 +160,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
mObservableUuidStore, this);
mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
mPowerManagerInternal = powerManagerInternal;
+ mCompanionExemptionProcessor = companionExemptionProcessor;
}
/** Initialize {@link DevicePresenceProcessor} */
@@ -404,7 +409,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* nearby (for "self-managed" associations).
*/
public boolean isDevicePresent(int associationId) {
- return mReportedSelfManagedDevices.contains(associationId)
+ return mConnectedSelfManagedDevices.contains(associationId)
|| mConnectedBtDevices.contains(associationId)
|| mNearbyBleDevices.contains(associationId)
|| mSimulated.contains(associationId);
@@ -451,7 +456,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* notifyDeviceAppeared()}
*/
public void onSelfManagedDeviceConnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
+ onDevicePresenceEvent(mConnectedSelfManagedDevices,
associationId, EVENT_SELF_MANAGED_APPEARED);
}
@@ -467,7 +472,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* notifyDeviceDisappeared()}
*/
public void onSelfManagedDeviceDisconnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
+ onDevicePresenceEvent(mConnectedSelfManagedDevices,
associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
@@ -475,7 +480,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* Marks a "self-managed" device as disconnected when binderDied.
*/
public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
+ onDevicePresenceEvent(mConnectedSelfManagedDevices,
associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
@@ -683,6 +688,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
if (association.shouldBindWhenPresent()) {
bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
+ mCompanionExemptionProcessor.exemptPackage(userId, packageName, true);
} else {
return;
}
@@ -715,6 +721,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
// Check if there are other devices associated to the app that are present.
if (!shouldBindPackage(userId, packageName)) {
mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
}
break;
default:
@@ -940,7 +947,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
mConnectedBtDevices.remove(id);
mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
+ mConnectedSelfManagedDevices.remove(id);
mSimulated.remove(id);
synchronized (mBtDisconnectedDevices) {
mBtDisconnectedDevices.remove(id);
@@ -1100,7 +1107,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
out.append("Companion Device Present: ");
if (mConnectedBtDevices.isEmpty()
&& mNearbyBleDevices.isEmpty()
- && mReportedSelfManagedDevices.isEmpty()) {
+ && mConnectedSelfManagedDevices.isEmpty()) {
out.append("<empty>\n");
return;
} else {
@@ -1130,11 +1137,11 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
}
out.append(" Self-Reported Devices: ");
- if (mReportedSelfManagedDevices.isEmpty()) {
+ if (mConnectedSelfManagedDevices.isEmpty()) {
out.append("<empty>\n");
} else {
out.append("\n");
- for (int associationId : mReportedSelfManagedDevices) {
+ for (int associationId : mConnectedSelfManagedDevices) {
AssociationInfo a = mAssociationStore.getAssociationById(associationId);
out.append(" ").append(a.toShortString()).append('\n');
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 71a182225013..2d3782fb3181 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -16,7 +16,7 @@
package com.android.server.companion.securechannel;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN;
import android.annotation.NonNull;
import android.content.Context;
@@ -69,7 +69,7 @@ public class SecureChannel {
private D2DConnectionContextV1 mConnectionContext;
private String mAlias;
- private int mVerificationResult;
+ private int mVerificationResult = FLAG_FAILURE_UNKNOWN;
private boolean mPskVerified;
@@ -498,7 +498,7 @@ public class SecureChannel {
private void exchangeAttestation()
throws IOException, GeneralSecurityException, BadHandleException, CryptoException {
- if (mVerificationResult == RESULT_SUCCESS) {
+ if (mVerificationResult == 0) {
Slog.d(TAG, "Remote attestation was already verified.");
return;
}
@@ -530,11 +530,11 @@ public class SecureChannel {
sendMessage(MessageType.AVF_RESULT, verificationResult);
byte[] remoteVerificationResult = readMessage(MessageType.AVF_RESULT);
- if (ByteBuffer.wrap(remoteVerificationResult).getInt() != RESULT_SUCCESS) {
+ if (ByteBuffer.wrap(remoteVerificationResult).getInt() != 0) {
throw new SecureChannelException("Remote device failed to verify local attestation.");
}
- if (mVerificationResult != RESULT_SUCCESS) {
+ if (mVerificationResult != 0) {
throw new SecureChannelException("Failed to verify remote attestation.");
}
@@ -549,7 +549,7 @@ public class SecureChannel {
return false;
}
// Is authenticated
- return mPskVerified || mVerificationResult == RESULT_SUCCESS;
+ return mPskVerified || mVerificationResult == 0;
}
// First byte indicates message type; 0 = CLIENT INIT, 1 = SERVER INIT
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 91ba9b3749fd..36083607bfcd 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -39,7 +39,9 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -55,13 +57,19 @@ public class CompanionTransportManager {
/** Association id -> Transport */
@GuardedBy("mTransports")
private final SparseArray<Transport> mTransports = new SparseArray<>();
+
+ // Use mTransports to synchronize both mTransports and mTransportsListeners to avoid deadlock
+ // between threads that access both
@NonNull
- @GuardedBy("mTransportsListeners")
+ @GuardedBy("mTransports")
private final RemoteCallbackList<IOnTransportsChangedListener> mTransportsListeners =
new RemoteCallbackList<>();
+
/** Message type -> IOnMessageReceivedListener */
+ @GuardedBy("mMessageListeners")
@NonNull
- private final SparseArray<IOnMessageReceivedListener> mMessageListeners = new SparseArray<>();
+ private final SparseArray<Set<IOnMessageReceivedListener>> mMessageListeners =
+ new SparseArray<>();
public CompanionTransportManager(Context context, AssociationStore associationStore) {
mContext = context;
@@ -72,7 +80,12 @@ public class CompanionTransportManager {
* Add a listener to receive callbacks when a message is received for the message type
*/
public void addListener(int message, @NonNull IOnMessageReceivedListener listener) {
- mMessageListeners.put(message, listener);
+ synchronized (mMessageListeners) {
+ if (!mMessageListeners.contains(message)) {
+ mMessageListeners.put(message, new HashSet<IOnMessageReceivedListener>());
+ }
+ mMessageListeners.get(message).add(listener);
+ }
synchronized (mTransports) {
for (int i = 0; i < mTransports.size(); i++) {
mTransports.valueAt(i).addListener(message, listener);
@@ -85,7 +98,7 @@ public class CompanionTransportManager {
*/
public void addListener(IOnTransportsChangedListener listener) {
Slog.i(TAG, "Registering OnTransportsChangedListener");
- synchronized (mTransportsListeners) {
+ synchronized (mTransports) {
mTransportsListeners.register(listener);
mTransportsListeners.broadcast(listener1 -> {
// callback to the current listener with all the associations of the transports
@@ -104,7 +117,7 @@ public class CompanionTransportManager {
* Remove the listener for receiving callbacks when any of the transports is changed
*/
public void removeListener(IOnTransportsChangedListener listener) {
- synchronized (mTransportsListeners) {
+ synchronized (mTransports) {
mTransportsListeners.unregister(listener);
}
}
@@ -113,7 +126,12 @@ public class CompanionTransportManager {
* Remove the listener to stop receiving calbacks when a message is received for the given type
*/
public void removeListener(int messageType, IOnMessageReceivedListener listener) {
- mMessageListeners.remove(messageType);
+ synchronized (mMessageListeners) {
+ if (!mMessageListeners.contains(messageType)) {
+ return;
+ }
+ mMessageListeners.get(messageType).remove(listener);
+ }
}
/**
@@ -189,7 +207,7 @@ public class CompanionTransportManager {
}
private void notifyOnTransportsChanged() {
- synchronized (mTransportsListeners) {
+ synchronized (mTransports) {
mTransportsListeners.broadcast(listener -> {
try {
listener.onTransportsChanged(getAssociationsWithTransport());
@@ -315,8 +333,12 @@ public class CompanionTransportManager {
}
private void addMessageListenersToTransport(Transport transport) {
- for (int i = 0; i < mMessageListeners.size(); i++) {
- transport.addListener(mMessageListeners.keyAt(i), mMessageListeners.valueAt(i));
+ synchronized (mMessageListeners) {
+ for (int i = 0; i < mMessageListeners.size(); i++) {
+ for (IOnMessageReceivedListener listener : mMessageListeners.valueAt(i)) {
+ transport.addListener(mMessageListeners.keyAt(i), listener);
+ }
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 8a5774e55ce2..986bd6c91e17 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -40,8 +40,8 @@ import libcore.util.EmptyArray;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
@@ -71,7 +71,8 @@ public abstract class Transport {
* the future to allow multiple listeners to receive callbacks for the same message type, the
* value of the map can be a list.
*/
- private final Map<Integer, IOnMessageReceivedListener> mListeners;
+ @GuardedBy("mListeners")
+ private final SparseArray<Set<IOnMessageReceivedListener>> mListeners = new SparseArray<>();
private OnTransportClosedListener mOnTransportClosed;
@@ -98,7 +99,6 @@ public abstract class Transport {
mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
mContext = context;
- mListeners = new HashMap<>();
}
/**
@@ -107,7 +107,12 @@ public abstract class Transport {
* @param listener Execute when a message with the type is received
*/
public void addListener(int message, IOnMessageReceivedListener listener) {
- mListeners.put(message, listener);
+ synchronized (mListeners) {
+ if (!mListeners.contains(message)) {
+ mListeners.put(message, new HashSet<IOnMessageReceivedListener>());
+ }
+ mListeners.get(message).add(listener);
+ }
}
public int getAssociationId() {
@@ -281,12 +286,19 @@ public abstract class Transport {
}
private void callback(int message, byte[] data) {
- if (mListeners.containsKey(message)) {
+ Set<IOnMessageReceivedListener> listenersToCall;
+ synchronized (mListeners) {
+ if (!mListeners.contains(message)) {
+ return;
+ }
+ listenersToCall = mListeners.get(message);
+ }
+ Slog.d(TAG, "Message 0x" + Integer.toHexString(message)
+ + " is received from associationId " + mAssociationId
+ + ", sending data length " + data.length + " to the listener(s).");
+ for (IOnMessageReceivedListener listener: listenersToCall) {
try {
- mListeners.get(message).onMessageReceived(getAssociationId(), data);
- Slog.d(TAG, "Message 0x" + Integer.toHexString(message)
- + " is received from associationId " + mAssociationId
- + ", sending data length " + data.length + " to the listener.");
+ listener.onMessageReceived(getAssociationId(), data);
} catch (RemoteException ignored) {
}
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index c927cd04cf64..f37e0c94caca 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -86,7 +86,7 @@ public final class PermissionsUtils {
@NonNull AssociationRequest request, int packageUid) {
enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid);
- if (request.isSelfManaged()) {
+ if (request.isSelfManaged() || request.getDeviceIcon() != null) {
enforcePermissionForRequestingSelfManaged(context, packageUid);
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ef39846f4750..8a4b1faf2df9 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -59,14 +59,14 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen
private int mObserverCount = 0;
@GuardedBy("mLock")
- private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+ private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
/**
* Mapping from camera ID to open camera app associations. Key is the camera id, value is the
* information of the app's uid and package name.
*/
@GuardedBy("mLock")
- private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+ private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
static class InjectionSessionData {
public int appUid;
@@ -179,6 +179,15 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen
Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
}
}
+ // Clean up camera injection sessions (if any).
+ synchronized (mLock) {
+ for (InjectionSessionData sessionData : mPackageToSessionData.values()) {
+ for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) {
+ session.close();
+ }
+ }
+ mPackageToSessionData.clear();
+ }
mCameraManager.unregisterAvailabilityCallback(this);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 4b9065bc7f72..6069e341521e 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -88,6 +88,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
/** Called when a secure window shows on the virtual display. */
void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo);
+ /** Called when a secure window is no longer shown on the virtual display. */
+ void onSecureWindowHidden(int displayId);
+
/** Returns true when an intent should be intercepted */
boolean shouldInterceptIntent(@NonNull Intent intent);
}
@@ -123,6 +126,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
private boolean mIsMirrorDisplay = false;
private final CountDownLatch mDisplayIdSetLatch = new CountDownLatch(1);
+ // Used for detecting changes in the window flags.
+ private int mCurrentWindowFlags = 0;
+
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
private final ArraySet<Integer> mRunningUids = new ArraySet<>();
@@ -371,12 +377,19 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
int displayId = waitAndGetDisplayId();
- // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
- // aware that the virtual display has a secure window on top.
- if ((windowFlags & FLAG_SECURE) != 0 && displayId != INVALID_DISPLAY) {
+ if (displayId != INVALID_DISPLAY) {
+ // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+ // aware that the virtual display has a secure window on top.
// Post callback on the main thread, so it doesn't block activity launching.
- mHandler.post(() -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+ if ((windowFlags & FLAG_SECURE) != 0 && (mCurrentWindowFlags & FLAG_SECURE) == 0) {
+ mHandler.post(
+ () -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+ }
+ if ((windowFlags & FLAG_SECURE) == 0 && (mCurrentWindowFlags & FLAG_SECURE) != 0) {
+ mHandler.post(() -> mActivityListener.onSecureWindowHidden(displayId));
+ }
}
+ mCurrentWindowFlags = windowFlags;
if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
activityInfo.packageName,
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 8d075db1c168..88b791046c87 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -256,8 +256,15 @@ public class SensorController {
Slog.e(TAG, "Received invalid ParcelFileDescriptor");
return BAD_VALUE;
}
+
+ SharedMemory sharedMemory;
+ try {
+ sharedMemory = SharedMemory.fromFileDescriptor(fd);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to create shared memory: " + e);
+ return BAD_VALUE;
+ }
final int channelHandle = sNextDirectChannelHandle.getAndIncrement();
- SharedMemory sharedMemory = SharedMemory.fromFileDescriptor(fd);
try {
mCallback.onDirectChannelCreated(channelHandle, sharedMemory);
} catch (RemoteException e) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index cd2dd3a27c9a..a1d621d8dd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -16,10 +16,13 @@
package com.android.server.companion.virtual;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
@@ -30,7 +33,6 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOAR
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
-import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -42,6 +44,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.virtual.ActivityPolicyExemption;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -94,10 +97,10 @@ import android.os.Build;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.Looper;
-import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -153,6 +156,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
+ private static final List<String> DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS = List.of(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
+
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
@@ -199,6 +205,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
+ private final PowerManager mPowerManager;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -209,6 +216,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@GuardedBy("mVirtualDeviceLock")
@Nullable
private LocaleList mLocaleList = null;
+ @GuardedBy("mVirtualDeviceLock")
+ private boolean mLockdownActive = false;
+ @GuardedBy("mVirtualDeviceLock")
+ private boolean mRequestedToBeAwake = true;
@NonNull
private final VirtualDevice mPublicVirtualDeviceObject;
@@ -301,6 +312,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
+ @Override
+ public void onSecureWindowHidden(int displayId) {
+ if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ try {
+ mActivityListener.onSecureWindowHidden(displayId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+ }
+ }
+ }
+
/**
* Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true
* if the intent matches any filter notifying the DisplayPolicyController to abort the
@@ -392,7 +414,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
VirtualDeviceParams params,
DisplayManagerGlobal displayManager,
VirtualCameraController virtualCameraController) {
- super(PermissionEnforcer.fromContext(context));
mVirtualDeviceLog = virtualDeviceLog;
mOwnerPackageName = attributionSource.getPackageName();
mAttributionSource = attributionSource;
@@ -414,6 +435,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mDevicePolicies = params.getDevicePolicies();
mDisplayManager = displayManager;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
+
+ if (mDevicePolicies.get(POLICY_TYPE_CLIPBOARD, DEVICE_POLICY_DEFAULT)
+ != DEVICE_POLICY_DEFAULT) {
+ if (mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ + "set a custom clipboard policy.");
+ }
+ }
+
+ int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
+ if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
+ if (mContext.checkCallingOrSelfPermission(ADD_ALWAYS_UNLOCKED_DISPLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ADD_ALWAYS_UNLOCKED_DISPLAY permission to "
+ + "create an always unlocked virtual device.");
+ }
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+ }
+ mBaseVirtualDisplayFlags = flags;
+
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
@@ -456,12 +499,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
: mParams.getAllowedActivities();
}
- int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
- if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
- flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
- }
- mBaseVirtualDisplayFlags = flags;
-
if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
final String imeId = mParams.getInputMethodComponent().flattenToShortString();
Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device "
@@ -471,6 +508,20 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
+ void onLockdownChanged(boolean lockdownActive) {
+ synchronized (mVirtualDeviceLock) {
+ if (lockdownActive != mLockdownActive) {
+ mLockdownActive = lockdownActive;
+ if (mLockdownActive) {
+ goToSleepInternal(PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF);
+ } else if (mRequestedToBeAwake) {
+ wakeUpInternal(PowerManager.WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
+ "android.server.companion.virtual:LOCKDOWN_ENDED");
+ }
+ }
+ }
+ }
+
@VisibleForTesting
SensorController getSensorControllerForTest() {
return mSensorController;
@@ -498,6 +549,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mAssociationInfo == null ? mParams.getName() : mAssociationInfo.getDisplayName();
}
+ String getDeviceProfile() {
+ return mAssociationInfo == null ? null : mAssociationInfo.getDeviceProfile();
+ }
+
/** Returns the public representation of the device. */
VirtualDevice getPublicVirtualDeviceObject() {
return mPublicVirtualDeviceObject;
@@ -518,10 +573,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
* object is created before the returned VirtualDeviceInternal one.
*/
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setListeners(@NonNull IVirtualDeviceActivityListener activityListener,
@NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
- super.setListeners_enforcePermission();
mActivityListener = Objects.requireNonNull(activityListener);
mSoundEffectListener = Objects.requireNonNull(soundEffectListener);
}
@@ -568,14 +621,45 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
+ public void goToSleep() {
+ checkCallerIsDeviceOwner();
+ synchronized (mVirtualDeviceLock) {
+ mRequestedToBeAwake = false;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ goToSleepInternal(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void wakeUp() {
+ checkCallerIsDeviceOwner();
+ synchronized (mVirtualDeviceLock) {
+ mRequestedToBeAwake = true;
+ if (mLockdownActive) {
+ Slog.w(TAG, "Cannot wake up device during lockdown.");
+ return;
+ }
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ wakeUpInternal(PowerManager.WAKE_REASON_POWER_BUTTON,
+ "android.server.companion.virtual:DEVICE_ON");
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(pendingIntent);
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplays.contains(displayId)) {
- throw new SecurityException("Display ID " + displayId
- + " not found for this virtual device");
- }
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
}
if (pendingIntent.isActivity()) {
try {
@@ -607,9 +691,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void addActivityPolicyExemption(@NonNull ActivityPolicyExemption exemption) {
- super.addActivityPolicyExemption_enforcePermission();
+ checkCallerIsDeviceOwner();
final int displayId = exemption.getDisplayId();
if (exemption.getComponentName() == null || displayId != Display.INVALID_DISPLAY) {
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
@@ -645,9 +728,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void removeActivityPolicyExemption(@NonNull ActivityPolicyExemption exemption) {
- super.removeActivityPolicyExemption_enforcePermission();
+ checkCallerIsDeviceOwner();
final int displayId = exemption.getDisplayId();
if (exemption.getComponentName() == null || displayId != Display.INVALID_DISPLAY) {
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
@@ -698,9 +780,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
- super.close_enforcePermission();
// Remove about-to-be-closed virtual device from the service before butchering it.
if (!mService.removeVirtualDevice(mDeviceId)) {
// Device is already closed.
@@ -775,11 +855,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAudioSessionStarting(int displayId,
@NonNull IAudioRoutingCallback routingCallback,
@Nullable IAudioConfigChangedCallback configChangedCallback) {
- super.onAudioSessionStarting_enforcePermission();
+ checkCallerIsDeviceOwner();
synchronized (mVirtualDeviceLock) {
checkDisplayOwnedByVirtualDeviceLocked(displayId);
if (mVirtualAudioController == null) {
@@ -793,9 +872,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAudioSessionEnded() {
- super.onAudioSessionEnded_enforcePermission();
+ checkCallerIsDeviceOwner();
synchronized (mVirtualDeviceLock) {
if (mVirtualAudioController != null) {
mVirtualAudioController.stopListening();
@@ -805,10 +883,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
- super.setDevicePolicy_enforcePermission();
+ checkCallerIsDeviceOwner();
if (!Flags.dynamicPolicy()) {
return;
}
@@ -818,8 +895,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
synchronized (mVirtualDeviceLock) {
mDevicePolicies.put(policyType, devicePolicy);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
- mVirtualDisplays.valueAt(i).getWindowPolicyController()
- .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (wrapper.isTrusted()) {
+ wrapper.getWindowPolicyController()
+ .setShowInHostDeviceRecents(
+ devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
}
}
break;
@@ -839,7 +920,20 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
break;
case POLICY_TYPE_CLIPBOARD:
if (Flags.crossDeviceClipboard()) {
+ if (devicePolicy == DEVICE_POLICY_CUSTOM
+ && mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ + "set a custom clipboard policy.");
+ }
synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (!wrapper.isTrusted() && !wrapper.isMirror()) {
+ throw new SecurityException("All displays must be trusted for "
+ + "devices with custom clipboard policy.");
+ }
+ }
mDevicePolicies.put(policyType, devicePolicy);
}
}
@@ -858,11 +952,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDevicePolicyForDisplay(int displayId,
@VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
- super.setDevicePolicyForDisplay_enforcePermission();
+ checkCallerIsDeviceOwner();
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
return;
}
@@ -870,8 +963,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
checkDisplayOwnedByVirtualDeviceLocked(displayId);
switch (policyType) {
case POLICY_TYPE_RECENTS:
- mVirtualDisplays.get(displayId).getWindowPolicyController()
- .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.get(displayId);
+ if (wrapper.isTrusted()) {
+ wrapper.getWindowPolicyController()
+ .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
break;
case POLICY_TYPE_ACTIVITY:
mVirtualDisplays.get(displayId).getWindowPolicyController()
@@ -885,9 +981,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
- super.createVirtualDpad_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
@@ -903,9 +998,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
- super.createVirtualKeyboard_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
@@ -925,9 +1019,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
- super.createVirtualMouse_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
@@ -942,10 +1035,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
@NonNull IBinder deviceToken) {
- super.createVirtualTouchscreen_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
@@ -961,10 +1053,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
@NonNull IBinder deviceToken) {
- super.createVirtualNavigationTouchpad_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
@@ -982,10 +1073,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualStylus(@NonNull VirtualStylusConfig config,
@NonNull IBinder deviceToken) {
- super.createVirtualStylus_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
Objects.requireNonNull(deviceToken);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
@@ -1002,10 +1092,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualRotaryEncoder(@NonNull VirtualRotaryEncoderConfig config,
@NonNull IBinder deviceToken) {
- super.createVirtualRotaryEncoder_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(config);
Objects.requireNonNull(deviceToken);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
@@ -1022,9 +1111,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterInputDevice(IBinder token) {
- super.unregisterInputDevice_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
mInputController.unregisterInputDevice(token);
@@ -1045,9 +1133,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
- super.sendDpadKeyEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendDpadKeyEvent(token, event);
@@ -1057,9 +1144,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
- super.sendKeyEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendKeyEvent(token, event);
@@ -1069,9 +1155,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
- super.sendButtonEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendButtonEvent(token, event);
@@ -1081,9 +1166,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
- super.sendTouchEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendTouchEvent(token, event);
@@ -1093,9 +1177,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
- super.sendRelativeEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendRelativeEvent(token, event);
@@ -1105,9 +1188,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
- super.sendScrollEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendScrollEvent(token, event);
@@ -1127,10 +1209,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendStylusMotionEvent(@NonNull IBinder token,
@NonNull VirtualStylusMotionEvent event) {
- super.sendStylusMotionEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(token);
Objects.requireNonNull(event);
final long ident = Binder.clearCallingIdentity();
@@ -1142,10 +1223,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendStylusButtonEvent(@NonNull IBinder token,
@NonNull VirtualStylusButtonEvent event) {
- super.sendStylusButtonEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(token);
Objects.requireNonNull(event);
final long ident = Binder.clearCallingIdentity();
@@ -1157,10 +1237,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendRotaryEncoderScrollEvent(@NonNull IBinder token,
@NonNull VirtualRotaryEncoderScrollEvent event) {
- super.sendRotaryEncoderScrollEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendRotaryEncoderScrollEvent(token, event);
@@ -1170,17 +1249,19 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setShowPointerIcon(boolean showPointerIcon) {
- super.setShowPointerIcon_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
- }
- final int[] displayIds = getDisplayIds();
- for (int i = 0; i < displayIds.length; ++i) {
- mInputController.setShowPointerIcon(showPointerIcon, displayIds[i]);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (wrapper.isTrusted() || wrapper.isMirror()) {
+ mInputController.setShowPointerIcon(
+ mDefaultShowPointerIcon, mVirtualDisplays.keyAt(i));
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1188,14 +1269,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
- super.setDisplayImePolicy_enforcePermission();
+ checkCallerIsDeviceOwner();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplays.contains(displayId)) {
- throw new SecurityException("Display ID " + displayId
- + " not found for this virtual device");
- }
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
}
final long ident = Binder.clearCallingIdentity();
try {
@@ -1206,10 +1283,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
public List<VirtualSensor> getVirtualSensorList() {
- super.getVirtualSensorList_enforcePermission();
+ checkCallerIsDeviceOwner();
return mSensorController.getSensorList();
}
@@ -1219,9 +1295,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
- super.sendSensorEvent_enforcePermission();
+ checkCallerIsDeviceOwner();
final long ident = Binder.clearCallingIdentity();
try {
return mSensorController.sendSensorEvent(token, event);
@@ -1231,10 +1306,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor,
IntentFilter filter) {
- super.registerIntentInterceptor_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(intentInterceptor);
Objects.requireNonNull(filter);
synchronized (mVirtualDeviceLock) {
@@ -1243,10 +1317,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterIntentInterceptor(
@NonNull IVirtualDeviceIntentInterceptor intentInterceptor) {
- super.unregisterIntentInterceptor_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(intentInterceptor);
synchronized (mVirtualDeviceLock) {
mIntentInterceptors.remove(intentInterceptor.asBinder());
@@ -1254,10 +1327,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void registerVirtualCamera(@NonNull VirtualCameraConfig cameraConfig)
throws RemoteException {
- super.registerVirtualCamera_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(cameraConfig);
if (mVirtualCameraController == null) {
throw new UnsupportedOperationException("Virtual camera controller is not available");
@@ -1266,10 +1338,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterVirtualCamera(@NonNull VirtualCameraConfig cameraConfig)
throws RemoteException {
- super.unregisterVirtualCamera_enforcePermission();
+ checkCallerIsDeviceOwner();
Objects.requireNonNull(cameraConfig);
if (mVirtualCameraController == null) {
throw new UnsupportedOperationException("Virtual camera controller is not available");
@@ -1278,10 +1349,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
- @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public String getVirtualCameraId(@NonNull VirtualCameraConfig cameraConfig)
throws RemoteException {
- super.getVirtualCameraId_enforcePermission();
Objects.requireNonNull(cameraConfig);
if (mVirtualCameraController == null) {
throw new UnsupportedOperationException("Virtual camera controller is not available");
@@ -1294,6 +1363,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return hasCustomAudioInputSupportInternal();
}
+ @Override
+ public boolean canCreateMirrorDisplays() {
+ return DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS.contains(getDeviceProfile());
+ }
+
private boolean hasCustomAudioInputSupportInternal() {
if (!Flags.vdmPublicApis()) {
return false;
@@ -1398,43 +1472,56 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return gwpc;
}
- int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
- @NonNull IVirtualDisplayCallback callback, String packageName) {
+ @Override // Binder call
+ public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+ @NonNull IVirtualDisplayCallback callback) {
+ checkCallerIsDeviceOwner();
+
+ int displayId;
+ boolean showPointer;
+ boolean isTrustedDisplay;
GenericWindowPolicyController gwpc;
synchronized (mVirtualDeviceLock) {
gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
- }
- int displayId;
- displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
- this, gwpc, packageName);
- boolean isMirrorDisplay =
- mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
- gwpc.setDisplayId(displayId, isMirrorDisplay);
+ displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
+ callback, this, gwpc, mOwnerPackageName);
+ boolean isMirrorDisplay =
+ mDisplayManagerInternal.getDisplayIdToMirror(displayId)
+ != Display.INVALID_DISPLAY;
+ gwpc.setDisplayId(displayId, isMirrorDisplay);
+ isTrustedDisplay =
+ (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+ == Display.FLAG_TRUSTED;
+ if (!isTrustedDisplay
+ && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+ throw new SecurityException("All displays must be trusted for devices with "
+ + "custom clipboard policy.");
+ }
- boolean showPointer;
- synchronized (mVirtualDeviceLock) {
if (mVirtualDisplays.contains(displayId)) {
gwpc.unregisterRunningAppsChangedListener(this);
throw new IllegalStateException(
"Virtual device already has a virtual display with ID " + displayId);
}
- PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
- mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+ PowerManager.WakeLock wakeLock =
+ isTrustedDisplay ? createAndAcquireWakeLockForDisplay(displayId) : null;
+ mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock,
+ isTrustedDisplay, isMirrorDisplay));
showPointer = mDefaultShowPointerIcon;
}
final long token = Binder.clearCallingIdentity();
try {
- mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setMousePointerAccelerationEnabled(false, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
- // WM throws a SecurityException if the display is untrusted.
- if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
- == Display.FLAG_TRUSTED) {
+ if (isTrustedDisplay) {
+ mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setDisplayImePolicy(displayId,
WindowManager.DISPLAY_IME_POLICY_LOCAL);
+ } else {
+ gwpc.setShowInHostDeviceRecents(true);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1447,6 +1534,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+ return null;
+ }
final long token = Binder.clearCallingIdentity();
try {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
@@ -1498,7 +1588,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return result;
}
-
void onVirtualDisplayRemoved(int displayId) {
/* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
* by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
@@ -1536,6 +1625,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
!= PackageManager.PERMISSION_GRANTED) {
synchronized (mVirtualDeviceLock) {
checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.get(displayId);
+ if (!wrapper.isTrusted() && !wrapper.isMirror()) {
+ throw new SecurityException(
+ "Cannot create input device associated with an untrusted display");
+ }
}
}
}
@@ -1549,6 +1643,41 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
+ private void checkCallerIsDeviceOwner() {
+ if (Binder.getCallingUid() != mOwnerUid) {
+ throw new SecurityException(
+ "Caller is not the owner of this virtual device");
+ }
+ }
+
+ void goToSleepInternal(@PowerManager.GoToSleepReason int reason) {
+ final long now = SystemClock.uptimeMillis();
+ synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (!wrapper.isTrusted() || wrapper.isMirror()) {
+ continue;
+ }
+ int displayId = mVirtualDisplays.keyAt(i);
+ mPowerManager.goToSleep(displayId, now, reason, /* flags= */ 0);
+ }
+ }
+ }
+
+ void wakeUpInternal(@PowerManager.WakeReason int reason, String details) {
+ final long now = SystemClock.uptimeMillis();
+ synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (!wrapper.isTrusted() || wrapper.isMirror()) {
+ continue;
+ }
+ int displayId = mVirtualDisplays.keyAt(i);
+ mPowerManager.wakeUp(now, reason, details, displayId);
+ }
+ }
+ }
+
/**
* Release resources tied to virtual display owned by this VirtualDevice instance.
*
@@ -1557,7 +1686,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
* @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
*/
private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
- virtualDisplayWrapper.getWakeLock().release();
+ virtualDisplayWrapper.releaseWakeLock();
virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
this);
}
@@ -1566,6 +1695,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mOwnerUid;
}
+ long getDimDurationMillis() {
+ return mParams.getDimDuration().toMillis();
+ }
+
+ long getScreenOffTimeoutMillis() {
+ return mParams.getScreenOffTimeout().toMillis();
+ }
+
@Override // Binder call
public int[] getDisplayIds() {
synchronized (mVirtualDeviceLock) {
@@ -1652,6 +1789,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mInputController.getInputDeviceDescriptors().values().stream().anyMatch(
inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId);
}
+
void playSoundEffect(int effectType) {
try {
mSoundEffectListener.onPlaySoundEffect(effectType);
@@ -1719,21 +1857,35 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final IVirtualDisplayCallback mToken;
private final GenericWindowPolicyController mWindowPolicyController;
private final PowerManager.WakeLock mWakeLock;
+ private final boolean mIsTrusted;
+ private final boolean mIsMirror;
VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
@NonNull GenericWindowPolicyController windowPolicyController,
- @NonNull PowerManager.WakeLock wakeLock) {
+ @Nullable PowerManager.WakeLock wakeLock, boolean isTrusted, boolean isMirror) {
mToken = Objects.requireNonNull(token);
mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
- mWakeLock = Objects.requireNonNull(wakeLock);
+ mWakeLock = wakeLock;
+ mIsTrusted = isTrusted;
+ mIsMirror = isMirror;
}
GenericWindowPolicyController getWindowPolicyController() {
return mWindowPolicyController;
}
- PowerManager.WakeLock getWakeLock() {
- return mWakeLock;
+ void releaseWakeLock() {
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ boolean isTrusted() {
+ return mIsTrusted;
+ }
+
+ boolean isMirror() {
+ return mIsMirror;
}
IVirtualDisplayCallback getToken() {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 97ed214b452b..e631403a9972 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -17,15 +17,18 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -40,13 +43,14 @@ import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualnative.IVirtualDeviceManagerNative;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplayConfig;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
@@ -68,6 +72,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -88,7 +93,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-
@SuppressLint("LongLogTag")
public class VirtualDeviceManagerService extends SystemService {
@@ -101,6 +105,11 @@ public class VirtualDeviceManagerService extends SystemService {
AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+ /** Enable default device camera access for apps running on virtual devices. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L;
+
/**
* A virtual device association id corresponding to no CDM association.
*/
@@ -110,7 +119,7 @@ public class VirtualDeviceManagerService extends SystemService {
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
private final VirtualDeviceManagerInternal mLocalService;
- private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
+ private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
@@ -123,12 +132,31 @@ public class VirtualDeviceManagerService extends SystemService {
private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
new CompanionDeviceManager.OnAssociationsChangedListener() {
@Override
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) {
syncVirtualDevicesToCdmAssociations(associations);
}
};
+ private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ final Set<Integer> mUsersInLockdown = new ArraySet<>();
+
+ StrongAuthTracker(Context context) {
+ super(context);
+ }
+
+ @Override
+ public synchronized void onStrongAuthRequiredChanged(int userId) {
+ if ((getStrongAuthForUser(userId) & STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) > 0) {
+ if (mUsersInLockdown.add(userId) && mUsersInLockdown.size() == 1) {
+ onLockdownChanged(true);
+ }
+ } else if (mUsersInLockdown.remove(userId) && mUsersInLockdown.isEmpty()) {
+ onLockdownChanged(false);
+ }
+ }
+ }
+ private StrongAuthTracker mStrongAuthTracker;
+
private final RemoteCallbackList<IVirtualDeviceListener> mVirtualDeviceListeners =
new RemoteCallbackList<>();
@@ -201,9 +229,23 @@ public class VirtualDeviceManagerService extends SystemService {
+ " will be available.");
}
}
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+ mStrongAuthTracker = new StrongAuthTracker(getContext());
+ new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
+ }
+ }
+
+ // Called when the global lockdown state changes, i.e. lockdown is considered active if any user
+ // is in lockdown mode, and inactive if no users are in lockdown mode.
+ void onLockdownChanged(boolean lockdownActive) {
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ mVirtualDevices.valueAt(i).onLockdownChanged(lockdownActive);
+ }
+ }
}
- void onCameraAccessBlocked(int appUid) {
+ private void onCameraAccessBlocked(int appUid) {
ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
@@ -215,7 +257,15 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
- CameraAccessController getCameraAccessController(UserHandle userHandle) {
+ private CameraAccessController getCameraAccessController(UserHandle userHandle,
+ VirtualDeviceParams params, String callingPackage) {
+ if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage,
+ userHandle)
+ && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()
+ && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS)
+ == DEVICE_POLICY_DEFAULT)) {
+ return null;
+ }
int userId = userHandle.getIdentifier();
synchronized (mVirtualDeviceManagerLock) {
for (int i = 0; i < mVirtualDevices.size(); i++) {
@@ -303,7 +353,6 @@ public class VirtualDeviceManagerService extends SystemService {
return true;
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) {
Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
synchronized (mVirtualDeviceManagerLock) {
@@ -346,7 +395,6 @@ public class VirtualDeviceManagerService extends SystemService {
cdm.removeOnAssociationsChangedListener(mCdmAssociationListener);
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
void onCdmAssociationsChanged(List<AssociationInfo> associations) {
ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>();
for (int i = 0; i < associations.size(); ++i) {
@@ -416,7 +464,7 @@ public class VirtualDeviceManagerService extends SystemService {
}
};
- @android.annotation.EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
public IVirtualDevice createVirtualDevice(
IBinder token,
@@ -462,7 +510,8 @@ public class VirtualDeviceManagerService extends SystemService {
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
- getCameraAccessController(userHandle);
+ getCameraAccessController(userHandle, params,
+ attributionSource.getPackageName());
final int deviceId = sNextUniqueIndex.getAndIncrement();
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
@@ -502,37 +551,6 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override // Binder call
- public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
- IVirtualDisplayCallback callback, IVirtualDevice virtualDevice, String packageName)
- throws RemoteException {
- Objects.requireNonNull(virtualDisplayConfig);
- final int callingUid = getCallingUid();
- if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
- throw new SecurityException(
- "Package name " + packageName + " does not belong to calling uid "
- + callingUid);
- }
- VirtualDeviceImpl virtualDeviceImpl;
- synchronized (mVirtualDeviceManagerLock) {
- virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
- if (virtualDeviceImpl == null) {
- throw new SecurityException(
- "Invalid VirtualDevice (deviceId = " + virtualDevice.getDeviceId()
- + ")");
- }
- }
- if (virtualDeviceImpl.getOwnerUid() != callingUid) {
- throw new SecurityException(
- "uid " + callingUid
- + " is not the owner of the supplied VirtualDevice (deviceId = "
- + virtualDevice.getDeviceId() + ")");
- }
-
- return virtualDeviceImpl.createVirtualDisplay(
- virtualDisplayConfig, callback, packageName);
- }
-
- @Override // Binder call
public List<VirtualDevice> getVirtualDevices() {
List<VirtualDevice> virtualDevices = new ArrayList<>();
synchronized (mVirtualDeviceManagerLock) {
@@ -573,7 +591,6 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
-
@Override // Binder call
public int getDeviceIdForDisplayId(int displayId) {
if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
@@ -907,6 +924,22 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override
+ public long getDimDurationMillisForDeviceId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis();
+ }
+ }
+
+ @Override
+ public long getScreenOffTimeoutMillisForDeviceId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice == null ? -1 : virtualDevice.getScreenOffTimeoutMillis();
+ }
+ }
+
+ @Override
public boolean isValidVirtualDeviceId(int deviceId) {
return mImpl.isValidVirtualDeviceId(deviceId);
}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
index 57c643bb08a1..84aa331efc1a 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -30,8 +30,6 @@ import android.app.contentsuggestions.ISelectionsCallback;
import android.app.contentsuggestions.SelectionsRequest;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
@@ -147,6 +145,7 @@ public class ContentSuggestionsManagerService extends
}
}
+ @SuppressWarnings("GuardedBy")
@Override
public void provideContextImage(
int userId,
@@ -157,28 +156,19 @@ public class ContentSuggestionsManagerService extends
}
enforceCaller(UserHandle.getCallingUserId(), "provideContextImage");
- HardwareBuffer snapshotBuffer = null;
- int colorSpaceId = 0;
-
+ TaskSnapshot snapshot = null;
// Skip taking TaskSnapshot when bitmap is provided.
if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
// Can block, so call before acquiring the lock.
- TaskSnapshot snapshot =
- mActivityTaskManagerInternal.getTaskSnapshotBlocking(taskId, false);
- if (snapshot != null) {
- snapshotBuffer = snapshot.getHardwareBuffer();
- ColorSpace colorSpace = snapshot.getColorSpace();
- if (colorSpace != null) {
- colorSpaceId = colorSpace.getId();
- }
- }
+ snapshot = mActivityTaskManagerInternal.getTaskSnapshotBlocking(
+ taskId, false /* isLowResolution */,
+ TaskSnapshot.REFERENCE_CONTENT_SUGGESTION);
}
synchronized (mLock) {
final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
if (service != null) {
- service.provideContextImageLocked(taskId, snapshotBuffer, colorSpaceId,
- imageContextRequestExtras);
+ service.provideContextImageLocked(taskId, snapshot, imageContextRequestExtras);
} else {
if (VERBOSE) {
Slog.v(TAG, "provideContextImageLocked: no service for " + userId);
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
index c8588e2d98e6..6f543a5a0916 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -27,15 +27,13 @@ import android.app.contentsuggestions.SelectionsRequest;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
+import android.window.TaskSnapshot;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
-import com.android.server.wm.ActivityTaskManagerInternal;
/**
* Per user delegate of {@link ContentSuggestionsManagerService}.
@@ -52,13 +50,9 @@ public final class ContentSuggestionsPerUserService extends
@GuardedBy("mLock")
private RemoteContentSuggestionsService mRemoteService;
- @NonNull
- private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
-
ContentSuggestionsPerUserService(
ContentSuggestionsManagerService master, Object lock, int userId) {
super(master, lock, userId);
- mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
}
@GuardedBy("mLock")
@@ -94,16 +88,15 @@ public final class ContentSuggestionsPerUserService extends
@GuardedBy("mLock")
void provideContextImageFromBitmapLocked(@NonNull Bundle bitmapContainingExtras) {
// No task or snapshot provided, the bitmap is contained in the extras
- provideContextImageLocked(-1, null, 0, bitmapContainingExtras);
+ provideContextImageLocked(-1, null, bitmapContainingExtras);
}
@GuardedBy("mLock")
- void provideContextImageLocked(int taskId, @Nullable HardwareBuffer snapshot,
- int colorSpaceIdForSnapshot, @NonNull Bundle imageContextRequestExtras) {
+ void provideContextImageLocked(int taskId, @Nullable TaskSnapshot snapshot,
+ @NonNull Bundle imageContextRequestExtras) {
RemoteContentSuggestionsService service = ensureRemoteServiceLocked();
if (service != null) {
- service.provideContextImage(taskId, snapshot, colorSpaceIdForSnapshot,
- imageContextRequestExtras);
+ service.provideContextImage(taskId, snapshot, imageContextRequestExtras);
}
}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
index 6e0a0da76d05..92fa3b256646 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
@@ -24,12 +24,12 @@ import android.app.contentsuggestions.ISelectionsCallback;
import android.app.contentsuggestions.SelectionsRequest;
import android.content.ComponentName;
import android.content.Context;
-import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.IBinder;
import android.service.contentsuggestions.ContentSuggestionsService;
import android.service.contentsuggestions.IContentSuggestionsService;
import android.text.format.DateUtils;
+import android.window.TaskSnapshot;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
@@ -67,10 +67,14 @@ public class RemoteContentSuggestionsService extends
return TIMEOUT_REMOTE_REQUEST_MILLIS;
}
- void provideContextImage(int taskId, @Nullable HardwareBuffer contextImage,
- int colorSpaceId, @NonNull Bundle imageContextRequestExtras) {
- scheduleAsyncRequest((s) -> s.provideContextImage(taskId, contextImage,
- colorSpaceId, imageContextRequestExtras));
+ void provideContextImage(int taskId, @Nullable TaskSnapshot snapshot,
+ @NonNull Bundle imageContextRequestExtras) {
+ scheduleAsyncRequest((s) -> {
+ s.provideContextImage(taskId, snapshot, imageContextRequestExtras);
+ if (snapshot != null) {
+ snapshot.removeReference(TaskSnapshot.REFERENCE_CONTENT_SUGGESTION);
+ }
+ });
}
void suggestContentSelections(
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index ea6351baf597..fd18fa856916 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -24,7 +24,8 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
-import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -261,24 +262,28 @@ public class ContextualSearchManagerService extends SystemService {
}
}
- private Intent getResolvedLaunchIntent() {
+ private Intent getResolvedLaunchIntent(int userId) {
synchronized (this) {
+ if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
// If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
String csPkgName = getContextualSearchPackageName();
if (csPkgName.isEmpty()) {
// Return null if csPackageName is not specified.
+ if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty");
return null;
}
Intent launchIntent = new Intent(
ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH);
launchIntent.setPackage(csPkgName);
- ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
- launchIntent, MATCH_FACTORY_ONLY);
+ ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(
+ launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
if (resolveInfo == null) {
+ if (DEBUG_USER) Log.w(TAG, "resolveInfo is null");
return null;
}
ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
if (componentName == null) {
+ if (DEBUG_USER) Log.w(TAG, "componentName is null");
return null;
}
launchIntent.setComponent(componentName);
@@ -286,9 +291,10 @@ public class ContextualSearchManagerService extends SystemService {
}
}
- private Intent getContextualSearchIntent(int entrypoint, CallbackToken mToken) {
- final Intent launchIntent = getResolvedLaunchIntent();
+ private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
+ final Intent launchIntent = getResolvedLaunchIntent(userId);
if (launchIntent == null) {
+ if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
return null;
}
@@ -341,6 +347,7 @@ public class ContextualSearchManagerService extends SystemService {
TYPE_NAVIGATION_BAR_PANEL,
TYPE_POINTER));
} else {
+ if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
shb = null;
}
final Bitmap bm = shb != null ? shb.asBitmap() : null;
@@ -444,7 +451,7 @@ public class ContextualSearchManagerService extends SystemService {
@Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
- if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
+ if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
enforcePermission("startContextualSearch");
final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
@@ -455,7 +462,8 @@ public class ContextualSearchManagerService extends SystemService {
// server has READ_FRAME_BUFFER permission to get the screenshot and because only
// the system server can invoke non-exported activities.
Binder.withCleanCallingIdentity(() -> {
- Intent launchIntent = getContextualSearchIntent(entrypoint, mToken);
+ Intent launchIntent =
+ getContextualSearchIntent(entrypoint, callingUserId, mToken);
if (launchIntent != null) {
int result = invokeContextualSearchIntent(launchIntent, callingUserId);
if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1f22f086284c..34a47ac4b416 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -61,7 +61,7 @@ java_genrule {
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
"--protolog-class com.android.internal.protolog.ProtoLog " +
- "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+ "--loggroups-class com.android.internal.protolog.WmProtoLogGroups " +
"--loggroups-jar $(location :protolog-groups) " +
"--viewer-config-file-path /etc/core.protolog.pb " +
"--legacy-viewer-config-file-path /system/etc/protolog.conf.json.gz " +
@@ -80,7 +80,7 @@ java_genrule {
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
"--protolog-class com.android.internal.protolog.ProtoLog " +
- "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+ "--loggroups-class com.android.internal.protolog.WmProtoLogGroups " +
"--loggroups-jar $(location :protolog-groups) " +
"--viewer-config-type json " +
"--viewer-config $(out) " +
@@ -97,7 +97,7 @@ java_genrule {
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
"--protolog-class com.android.internal.protolog.ProtoLog " +
- "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+ "--loggroups-class com.android.internal.protolog.WmProtoLogGroups " +
"--loggroups-jar $(location :protolog-groups) " +
"--viewer-config-type proto " +
"--viewer-config $(out) " +
@@ -127,6 +127,8 @@ java_library_static {
"platform_service_defaults",
"android.hardware.power-java_shared",
"latest_android_hardware_broadcastradio_java_static",
+ "services_crashrecovery_stubs_conditionally",
+ "ondeviceintelligence_conditionally",
],
srcs: [
":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -160,6 +162,7 @@ java_library_static {
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V3-java",
+ "androidx.annotation_annotation",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"keepanno-annotations",
@@ -180,6 +183,7 @@ java_library_static {
static_libs: [
"android.frameworks.vibrator-V1-java", // AIDL
+ "android.frameworks.devicestate-V1-java", // AIDL
"android.hardware.authsecret-V1.0-java",
"android.hardware.authsecret-V1-java",
"android.hardware.boot-V1.0-java", // HIDL
@@ -190,7 +194,7 @@ java_library_static {
"android.hardware.health-V1.0-java", // HIDL
"android.hardware.health-V2.0-java", // HIDL
"android.hardware.health-V2.1-java", // HIDL
- "android.hardware.health-V3-java", // AIDL
+ "android.hardware.health-V4-java", // AIDL
"android.hardware.health-translate-java",
"android.hardware.light-V1-java",
"android.hardware.security.authgraph-V1-java",
@@ -222,6 +226,7 @@ java_library_static {
"securebox",
"apache-commons-math",
"battery_saver_flag_lib",
+ "guava",
"notification_flags_lib",
"power_hint_flags_lib",
"biometrics_flags_lib",
@@ -245,6 +250,9 @@ java_library_static {
"locksettings_flags_lib",
"MmdProperties",
"mmd_flags_lib",
+ "profiling_flags_lib",
+ "android.adpf.sessionmanager_aidl-java",
+ "uprobestats_flags_java_lib",
],
javac_shard_size: 50,
javacflags: [
@@ -260,6 +268,13 @@ java_library_static {
"FlaggedApi",
],
},
+ jarjar_rules: ":services-jarjar-rules",
+ apex_available: ["//apex_available:platform"],
+}
+
+filegroup {
+ name: "services-jarjar-rules",
+ srcs: ["services-jarjar-rules.txt"],
}
java_genrule {
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 0713999d4354..289935acd5e2 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -41,6 +41,7 @@ public abstract class BatteryStatsInternal {
public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3;
public static final int CPU_WAKEUP_SUBSYSTEM_SENSOR = 4;
public static final int CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA = 5;
+ public static final int CPU_WAKEUP_SUBSYSTEM_BLUETOOTH = 6;
/** @hide */
@IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
@@ -50,6 +51,7 @@ public abstract class BatteryStatsInternal {
CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
CPU_WAKEUP_SUBSYSTEM_SENSOR,
CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
+ CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CpuWakeupSubsystem {
@@ -99,6 +101,14 @@ public abstract class BatteryStatsInternal {
public abstract void noteCpuWakingNetworkPacket(Network network, long elapsedMillis, int uid);
/**
+ * Informs battery stats of a sysproxy packet that woke up the CPU
+ *
+ * @param uid The uid that received the packet.
+ * @param elapsedMillis The time of the packet's arrival in elapsed timebase.
+ */
+ public abstract void noteCpuWakingBluetoothProxyPacket(int uid, long elapsedMillis);
+
+ /**
* Informs battery stats of binder stats for the given work source UID.
*/
public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount,
@@ -122,4 +132,7 @@ public abstract class BatteryStatsInternal {
* @param uids the uids of all apps that have any alarm in this batch.
*/
public abstract void noteWakingAlarmBatch(long elapsedMillis, int... uids);
+
+ /** See PowerStatsUidResolver.mapUid(). */
+ public abstract int getOwnerUid(int uid);
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 59dea099c2a1..4cf17ae3984d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,6 +22,9 @@ import static android.os.Flags.stateOfHealthPublic;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
+import static java.lang.Math.abs;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -42,11 +45,13 @@ import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.DropBoxManager;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
+import android.os.Looper;
import android.os.OsProtoEnums;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -64,8 +69,10 @@ import android.service.battery.BatteryServiceDumpProto;
import android.sysprop.PowerProperties;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.SomeArgs;
@@ -83,6 +90,7 @@ import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/**
@@ -144,23 +152,116 @@ public final class BatteryService extends SystemService {
private final Handler mHandler;
private final Object mLock = new Object();
-
+ private final ConditionVariable mConditionVariable = new ConditionVariable();
private HealthInfo mHealthInfo;
private final HealthInfo mLastHealthInfo = new HealthInfo();
private boolean mBatteryLevelCritical;
- private int mLastBatteryStatus;
- private int mLastBatteryHealth;
- private boolean mLastBatteryPresent;
- private int mLastBatteryLevel;
- private int mLastBatteryVoltage;
- private int mLastBatteryTemperature;
- private boolean mLastBatteryLevelCritical;
- private int mLastMaxChargingCurrent;
- private int mLastMaxChargingVoltage;
- private int mLastChargeCounter;
- private int mLastBatteryCycleCount;
- private int mLastChargingState;
- private int mLastBatteryCapacityLevel;
+
+ /**
+ * {@link HealthInfo#batteryStatus} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryStatus;
+ /**
+ * {@link HealthInfo#batteryHealth} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryHealth;
+ /**
+ * {@link HealthInfo#batteryPresent} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private boolean mLastBroadcastBatteryPresent;
+ /**
+ * {@link HealthInfo#batteryLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryLevel;
+ /**
+ * {@link HealthInfo#batteryVoltageMillivolts} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryVoltage;
+ /**
+ * {@link HealthInfo#batteryTemperatureTenthsCelsius} value when
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryTemperature;
+ /**
+ * {@link #mBatteryLevelCritical} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: These values may be used for internal operations and/or to determine whether to trigger
+ * the broadcast or not.
+ */
+ private boolean mLastBroadcastBatteryLevelCritical;
+ /**
+ * {@link HealthInfo#maxChargingCurrentMicroamps} value when
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastMaxChargingCurrent;
+ /**
+ * {@link HealthInfo#maxChargingVoltageMicrovolts} value when
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastMaxChargingVoltage;
+ /**
+ * {@link HealthInfo#batteryChargeCounterUah} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastChargeCounter;
+ /**
+ * {@link HealthInfo#batteryCycleCount} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryCycleCount;
+ /**
+ * {@link HealthInfo#chargingState} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastChargingState;
+ /**
+ * {@link HealthInfo#batteryCapacityLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryCapacityLevel;
+ /**
+ * {@link #mPlugType} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: These values may be used for internal operations and/or to determine whether to trigger
+ * the broadcast or not.
+ */
+ private int mLastBroadcastPlugType = -1; // Extra state so we can detect first run
+ /**
+ * {@link #mInvalidCharger} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: These values may be used for internal operations and/or to determine whether to trigger
+ * the broadcast or not.
+ */
+ private int mLastBroadcastInvalidCharger;
/**
* The last seen charging policy. This requires the
* {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -171,7 +272,6 @@ public final class BatteryService extends SystemService {
private int mSequence = 1;
private int mInvalidCharger;
- private int mLastInvalidCharger;
private int mLowBatteryWarningLevel;
private int mLastLowBatteryWarningLevel;
@@ -183,7 +283,6 @@ public final class BatteryService extends SystemService {
private static String sSystemUiPackage;
private int mPlugType;
- private int mLastPlugType = -1; // Extra state so we can detect first run
private boolean mBatteryLevelLow;
@@ -196,6 +295,27 @@ public final class BatteryService extends SystemService {
private boolean mUpdatesStopped;
private boolean mBatteryInputSuspended;
+ /**
+ * Time when the voltage was updated last by HAL and we sent the
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+ * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+ * so it is possible that voltage was updated but we did not send the broadcast so in that
+ * case we do not update the time.
+ */
+ @VisibleForTesting
+ public long mLastBroadcastVoltageUpdateTime;
+ /**
+ * Time when the max charging current was updated last by HAL and we sent the
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+ * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+ * so it is possible that max current was updated but we did not send the broadcast so in that
+ * case we do not update the time.
+ */
+ @VisibleForTesting
+ public long mLastBroadcastMaxChargingCurrentUpdateTime;
+
+ private boolean mIsFirstBatteryChangedUpdate = true;
+
private Led mLed;
private boolean mSentLowBatteryBroadcast = false;
@@ -210,7 +330,8 @@ public final class BatteryService extends SystemService {
private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
- private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
+ @VisibleForTesting
+ public static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
@@ -233,6 +354,30 @@ public final class BatteryService extends SystemService {
private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2;
private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast and update the temperature value when the temp change is greater or
+ * equals to 1 degree celsius.
+ */
+ private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
+ * fluctuation of at least 1%.
+ */
+ private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
+ /**
+ * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
+ * fluctuation of at least 1%.
+ */
+ private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last max charging current was updated at least 5 seconds back.
+ */
+ private static final int TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS = 5000;
+
private final Handler.Callback mLocalCallback = msg -> {
switch (msg.what) {
case MSG_BROADCAST_BATTERY_CHANGED: {
@@ -282,10 +427,19 @@ public final class BatteryService extends SystemService {
};
public BatteryService(Context context) {
+ this(context, Objects.requireNonNull(Looper.myLooper(),
+ "BatteryService uses handler!! Can't create handler inside thread that has not "
+ + "called Looper.prepare()"));
+ }
+
+ @VisibleForTesting
+ public BatteryService(Context context, @NonNull Looper looper) {
super(context);
+ Objects.requireNonNull(looper);
+
mContext = context;
- mHandler = new Handler(mLocalCallback, true /*async*/);
+ mHandler = new Handler(looper, mLocalCallback, true /*async*/);
mLed = new Led(context, getLocalService(LightsManager.class));
mBatteryStats = BatteryStatsService.getService();
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -379,17 +533,10 @@ public final class BatteryService extends SystemService {
// existing service in a near future. Wait for this.update() to instantiate
// the initial mHealthInfo.
long beforeWait = SystemClock.uptimeMillis();
- synchronized (mLock) {
- while (mHealthInfo == null) {
- Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait) +
- "ms for callbacks. Waiting another " + HEALTH_HAL_WAIT_MS + " ms...");
- try {
- mLock.wait(HEALTH_HAL_WAIT_MS);
- } catch (InterruptedException ex) {
- Slog.i(TAG, "health: InterruptedException when waiting for update. "
- + " Continuing...");
- }
- }
+ if (mHealthInfo == null) {
+ Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
+ + "ms for callbacks. Waiting another " + HEALTH_HAL_WAIT_MS + " ms...");
+ mConditionVariable.block(HEALTH_HAL_WAIT_MS);
}
Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
@@ -442,7 +589,7 @@ public final class BatteryService extends SystemService {
private boolean shouldSendBatteryLowLocked() {
final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
- final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+ final boolean oldPlugged = mLastBroadcastPlugType != BATTERY_PLUGGED_NONE;
/* The ACTION_BATTERY_LOW broadcast is sent in these situations:
* - is just un-plugged (previously was plugged) and battery level is
@@ -453,7 +600,7 @@ public final class BatteryService extends SystemService {
return !plugged
&& mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
- && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel
+ && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel
|| mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel);
}
@@ -521,7 +668,13 @@ public final class BatteryService extends SystemService {
}
}
- private void update(android.hardware.health.HealthInfo info) {
+ /**
+ * Updates the healthInfo and triggers the broadcast.
+ *
+ * @param info the new health info
+ */
+ @VisibleForTesting
+ public void update(android.hardware.health.HealthInfo info) {
traceBegin("HealthInfoUpdate");
Trace.traceCounter(
@@ -535,7 +688,7 @@ public final class BatteryService extends SystemService {
mHealthInfo = info;
// Process the new values.
processValuesLocked(false);
- mLock.notifyAll(); // for any waiters on new info
+ mConditionVariable.open();
} else {
copyV1Battery(mLastHealthInfo, info);
}
@@ -562,8 +715,8 @@ public final class BatteryService extends SystemService {
long dischargeDuration = 0;
mBatteryLevelCritical =
- mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
+ mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
mPlugType = plugType(mHealthInfo);
if (DEBUG) {
@@ -597,24 +750,28 @@ public final class BatteryService extends SystemService {
mHandler.post(this::notifyChargingPolicyChanged);
}
+ final boolean includeChargeCounter =
+ !com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()
+ && mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter;
+
if (force
- || (mHealthInfo.batteryStatus != mLastBatteryStatus
- || mHealthInfo.batteryHealth != mLastBatteryHealth
- || mHealthInfo.batteryPresent != mLastBatteryPresent
- || mHealthInfo.batteryLevel != mLastBatteryLevel
- || mPlugType != mLastPlugType
- || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage
- || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature
- || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent
- || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage
- || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
- || mInvalidCharger != mLastInvalidCharger
- || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
- || mHealthInfo.chargingState != mLastChargingState
- || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) {
-
- if (mPlugType != mLastPlugType) {
- if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+ || (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+ || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+ || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+ || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+ || mPlugType != mLastBroadcastPlugType
+ || mHealthInfo.batteryVoltageMillivolts != mLastBroadcastBatteryVoltage
+ || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBroadcastBatteryTemperature
+ || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+ || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+ || includeChargeCounter
+ || mInvalidCharger != mLastBroadcastInvalidCharger
+ || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+ || mHealthInfo.chargingState != mLastBroadcastChargingState
+ || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel)) {
+
+ if (mPlugType != mLastBroadcastPlugType) {
+ if (mLastBroadcastPlugType == BATTERY_PLUGGED_NONE) {
// discharging -> charging
mChargeStartLevel = mHealthInfo.batteryLevel;
mChargeStartTime = SystemClock.elapsedRealtime();
@@ -628,7 +785,8 @@ public final class BatteryService extends SystemService {
// There's no value in this data unless we've discharged at least once and the
// battery level has changed; so don't log until it does.
- if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
+ if (mDischargeStartTime != 0
+ && mDischargeStartLevel != mHealthInfo.batteryLevel) {
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
logOutlier = true;
EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
@@ -645,7 +803,7 @@ public final class BatteryService extends SystemService {
if (mChargeStartTime != 0 && chargeDuration != 0) {
final LogMaker builder = new LogMaker(MetricsEvent.ACTION_CHARGE);
builder.setType(MetricsEvent.TYPE_DISMISS);
- builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastPlugType);
+ builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastBroadcastPlugType);
builder.addTaggedData(MetricsEvent.FIELD_CHARGING_DURATION_MILLIS,
chargeDuration);
builder.addTaggedData(MetricsEvent.FIELD_BATTERY_LEVEL_START,
@@ -657,19 +815,20 @@ public final class BatteryService extends SystemService {
mChargeStartTime = 0;
}
}
- if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.batteryPresent != mLastBatteryPresent ||
- mPlugType != mLastPlugType) {
+ if (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+ || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+ || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+ || mPlugType != mLastBroadcastPlugType) {
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
- mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+ mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+ mHealthInfo.batteryPresent ? 1 : 0,
mPlugType, mHealthInfo.batteryTechnology);
SystemProperties.set(
"debug.tracing.battery_status",
Integer.toString(mHealthInfo.batteryStatus));
SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType));
}
- if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
+ if (mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
// too noisy.
EventLog.writeEvent(
@@ -678,8 +837,8 @@ public final class BatteryService extends SystemService {
mHealthInfo.batteryVoltageMillivolts,
mHealthInfo.batteryTemperatureTenthsCelsius);
}
- if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
- mPlugType == BATTERY_PLUGGED_NONE) {
+ if (mBatteryLevelCritical && !mLastBroadcastBatteryLevelCritical
+ && mPlugType == BATTERY_PLUGGED_NONE) {
// We want to make sure we log discharge cycle outliers
// if the battery is about to die.
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
@@ -690,7 +849,7 @@ public final class BatteryService extends SystemService {
// Should we now switch in to low battery mode?
if (mPlugType == BATTERY_PLUGGED_NONE
&& mHealthInfo.batteryStatus !=
- BatteryManager.BATTERY_STATUS_UNKNOWN
+ BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
@@ -698,7 +857,7 @@ public final class BatteryService extends SystemService {
// Should we now switch out of low battery mode?
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
- } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
} else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
// If being forced, the previous state doesn't matter, we will just
@@ -712,7 +871,7 @@ public final class BatteryService extends SystemService {
// Separate broadcast is sent for power connected / not connected
// since the standard intent will not wake any applications and some
// applications may want to have smart behavior based on this.
- if (mPlugType != 0 && mLastPlugType == 0) {
+ if (mPlugType != 0 && mLastBroadcastPlugType == 0) {
final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -732,8 +891,7 @@ public final class BatteryService extends SystemService {
}
});
}
- }
- else if (mPlugType == 0 && mLastPlugType != 0) {
+ } else if (mPlugType == 0 && mLastBroadcastPlugType != 0) {
final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -803,8 +961,14 @@ public final class BatteryService extends SystemService {
// We are doing this after sending the above broadcasts, so anything processing
// them will get the new sequence number at that point. (See for example how testing
// of JobScheduler's BatteryController works.)
- sendBatteryChangedIntentLocked(force);
- if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) {
+
+ boolean rateLimitBatteryChangedBroadcast = rateLimitBatteryChangedBroadcast(force);
+
+ if (!rateLimitBatteryChangedBroadcast) {
+ sendBatteryChangedIntentLocked(force);
+ }
+ if (mLastBroadcastBatteryLevel != mHealthInfo.batteryLevel
+ || mLastBroadcastPlugType != mPlugType) {
sendBatteryLevelChangedIntentLocked();
}
@@ -817,21 +981,24 @@ public final class BatteryService extends SystemService {
logOutlierLocked(dischargeDuration);
}
- mLastBatteryStatus = mHealthInfo.batteryStatus;
- mLastBatteryHealth = mHealthInfo.batteryHealth;
- mLastBatteryPresent = mHealthInfo.batteryPresent;
- mLastBatteryLevel = mHealthInfo.batteryLevel;
- mLastPlugType = mPlugType;
- mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
- mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
- mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
- mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
- mLastChargeCounter = mHealthInfo.batteryChargeCounterUah;
- mLastBatteryLevelCritical = mBatteryLevelCritical;
- mLastInvalidCharger = mInvalidCharger;
- mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
- mLastChargingState = mHealthInfo.chargingState;
- mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+ // Only update the values when we send the broadcast
+ if (!rateLimitBatteryChangedBroadcast) {
+ mLastBroadcastBatteryStatus = mHealthInfo.batteryStatus;
+ mLastBroadcastBatteryHealth = mHealthInfo.batteryHealth;
+ mLastBroadcastBatteryPresent = mHealthInfo.batteryPresent;
+ mLastBroadcastBatteryLevel = mHealthInfo.batteryLevel;
+ mLastBroadcastPlugType = mPlugType;
+ mLastBroadcastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
+ mLastBroadcastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
+ mLastBroadcastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
+ mLastBroadcastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
+ mLastBroadcastChargeCounter = mHealthInfo.batteryChargeCounterUah;
+ mLastBroadcastBatteryLevelCritical = mBatteryLevelCritical;
+ mLastBroadcastInvalidCharger = mInvalidCharger;
+ mLastBroadcastBatteryCycleCount = mHealthInfo.batteryCycleCount;
+ mLastBroadcastChargingState = mHealthInfo.chargingState;
+ mLastBroadcastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+ }
}
}
@@ -1095,6 +1262,97 @@ public final class BatteryService extends SystemService {
}
}
+ /**
+ * Rate limit's the broadcast based on the changes in temp, voltage and chargeCounter.
+ */
+ private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) {
+ if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
+ return false;
+ }
+ if (mIsFirstBatteryChangedUpdate) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ mIsFirstBatteryChangedUpdate = false;
+ return false;
+ }
+
+ final boolean voltageUpdated =
+ mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
+ final boolean temperatureUpdated =
+ mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+ final boolean maxChargingCurrentUpdated =
+ mLastBroadcastMaxChargingCurrent != mHealthInfo.maxChargingCurrentMicroamps;
+ final boolean otherStatesUpdated = forceUpdate
+ || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+ || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+ || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+ || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+ || mPlugType != mLastBroadcastPlugType
+ || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+ || mInvalidCharger != mLastBroadcastInvalidCharger
+ || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+ || mHealthInfo.chargingState != mLastBroadcastChargingState
+ || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel;
+
+ // We only rate limit based on changes in the temp, voltage.
+ if (otherStatesUpdated) {
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ final float basePointDiff =
+ (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts)
+ / mLastBroadcastBatteryVoltage;
+
+ // We only send the broadcast if voltage change is greater than 1% and last voltage
+ // update was sent at least 20 seconds back.
+ if (voltageUpdated
+ && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE
+ && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime
+ >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ // Only send the broadcast if the temperature update is greater than 1 degree celsius.
+ if (temperatureUpdated
+ && abs(
+ mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius)
+ >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) {
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ if (maxChargingCurrentUpdated
+ && SystemClock.elapsedRealtime() - mLastBroadcastMaxChargingCurrentUpdateTime
+ >= TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ return true;
+ }
+
class Shell extends ShellCommand {
@Override
public int onCommand(String cmd) {
@@ -1117,6 +1375,8 @@ public final class BatteryService extends SystemService {
getSetOptions += "|current_now|current_average";
}
pw.println(" get [-f] [" + getSetOptions + "]");
+ pw.println(" Gets the value of a battery state.");
+ pw.println(" -f: force to get the latest property value.");
pw.println(" set [-f] [" + getSetOptions + "] <value>");
pw.println(" Force a battery property value, freezing battery state.");
pw.println(" -f: force a battery change broadcast be sent, prints new sequence.");
@@ -1163,8 +1423,15 @@ public final class BatteryService extends SystemService {
if (key == null) {
pw.println("No property specified");
return -1;
+ }
+ // Update the health info.
+ if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ mConditionVariable.close();
+ updateHealthInfo();
+ mConditionVariable.block(HEALTH_HAL_WAIT_MS);
}
+
switch (key) {
case "present":
pw.println(mHealthInfo.batteryPresent);
@@ -1192,17 +1459,11 @@ public final class BatteryService extends SystemService {
break;
case "current_now":
if (batteryServiceSupportCurrentAdbCommand()) {
- if ((opts & OPTION_FORCE_UPDATE) != 0) {
- updateHealthInfo();
- }
pw.println(mHealthInfo.batteryCurrentMicroamps);
}
break;
case "current_average":
if (batteryServiceSupportCurrentAdbCommand()) {
- if ((opts & OPTION_FORCE_UPDATE) != 0) {
- updateHealthInfo();
- }
pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
}
break;
@@ -1394,6 +1655,9 @@ public final class BatteryService extends SystemService {
pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
pw.println(" Dock powered: " + mHealthInfo.chargerDockOnline);
pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps);
+ pw.println(" Time when the latest updated value of the Max charging current was"
+ + " sent via battery changed broadcast: "
+ + TimeUtils.formatDuration(mLastBroadcastMaxChargingCurrentUpdateTime));
pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts);
pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounterUah);
pw.println(" status: " + mHealthInfo.batteryStatus);
@@ -1402,6 +1666,11 @@ public final class BatteryService extends SystemService {
pw.println(" level: " + mHealthInfo.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
+ pw.println(" Time when the latest updated value of the voltage was sent via "
+ + "battery changed broadcast: "
+ + TimeUtils.formatDuration(mLastBroadcastVoltageUpdateTime));
+ pw.println(" The last voltage value sent via the battery changed broadcast: "
+ + mLastBroadcastBatteryVoltage);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
pw.println(" technology: " + mHealthInfo.batteryTechnology);
pw.println(" Charging state: " + mHealthInfo.chargingState);
@@ -1460,6 +1729,11 @@ public final class BatteryService extends SystemService {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ @VisibleForTesting
+ public Handler getHandlerForTest() {
+ return mHandler;
+ }
+
@SuppressLint("AndroidFrameworkRequiresPermission")
private static void sendBroadcastToAllUsers(Context context, Intent intent,
Bundle options) {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 020aed9a505b..ec0e57a2ccf0 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -65,6 +65,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -100,6 +101,9 @@ import java.util.Map;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+
/**
* @hide
*/
@@ -137,6 +141,10 @@ public class BinaryTransparencyService extends SystemService {
static final int MBA_STATUS_NEW_INSTALL = 3;
// used for indicating newly installed MBAs that are updated (but unused currently)
static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+ // used for indicating preloaded MBAs that are downgraded
+ static final int MBA_STATUS_DOWNGRADED_PRELOADED = 5;
+ // used for indicating MBAs that are uninstalled
+ static final int MBA_STATUS_UNINSTALLED = 6;
@VisibleForTesting
static final String KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION =
@@ -201,7 +209,9 @@ public class BinaryTransparencyService extends SystemService {
* @param mbaStatus Assign this value of MBA status to the returned elements.
* @return a @{@code List<IBinaryTransparencyService.AppInfo>}
*/
- private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ @VisibleForTesting
+ @NonNull
+ List<IBinaryTransparencyService.AppInfo> collectAppInfo(
PackageState packageState, int mbaStatus) {
// compute content digest
if (DEBUG) {
@@ -335,26 +345,28 @@ public class BinaryTransparencyService extends SystemService {
+ " packages after considering APEXs.");
}
- // proceed with all preloaded apps
- List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
- collectAllUpdatedPreloadInfo(packagesMeasured);
- for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
- packagesMeasured.putBoolean(appInfo.packageName, true);
- writeAppInfoToLog(appInfo);
- }
- if (DEBUG) {
- Slog.d(TAG, "Measured " + packagesMeasured.size()
- + " packages after considering preloads");
- }
-
- if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
- // lastly measure all newly installed MBAs
- List<IBinaryTransparencyService.AppInfo> allMbaInfo =
- collectAllSilentInstalledMbaInfo(packagesMeasured);
- for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+ if (!android.app.Flags.backgroundInstallControlCallbackApi()) {
+ // proceed with all preloaded apps
+ List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
+ collectAllUpdatedPreloadInfo(packagesMeasured);
+ for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
packagesMeasured.putBoolean(appInfo.packageName, true);
writeAppInfoToLog(appInfo);
}
+ if (DEBUG) {
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages after considering preloads");
+ }
+
+ if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
+ // lastly measure all newly installed MBAs
+ List<IBinaryTransparencyService.AppInfo> allMbaInfo =
+ collectAllSilentInstalledMbaInfo(packagesMeasured);
+ for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+ packagesMeasured.putBoolean(appInfo.packageName, true);
+ writeAppInfoToLog(appInfo);
+ }
+ }
}
long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
digestAllPackagesLatency.logSample(timeSpentMeasuring);
@@ -464,7 +476,8 @@ public class BinaryTransparencyService extends SystemService {
apexInfo.signerDigests);
}
- private void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
+ @VisibleForTesting
+ void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
// Must order by the proto's field number.
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
appInfo.packageName,
@@ -1158,6 +1171,94 @@ public class BinaryTransparencyService extends SystemService {
}
/**
+ * Receive callbacks from BIC to write silently installed apps to log
+ *
+ * TODO: Add a host test for testing registration and callback of BicCallbackHandler
+ * b/380002484
+ */
+ @VisibleForTesting
+ static class BicCallbackHandler extends IRemoteCallback.Stub {
+ private static final String BIC_CALLBACK_HANDLER_TAG = TAG + ".BicCallbackHandler";
+
+ private static final int INSTALL_EVENT_TYPE_UNSET = -1;
+
+ private final IBicAppInfoHelper mBicAppInfoHelper;
+
+ @VisibleForTesting
+ BicCallbackHandler(IBicAppInfoHelper bicAppInfoHelper) {
+ mBicAppInfoHelper = bicAppInfoHelper;
+ }
+
+ @Override
+ public void sendResult(Bundle data) {
+ String packageName = data.getString(
+ BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY);
+ int installType = data.getInt(
+ BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+ INSTALL_EVENT_TYPE_UNSET);
+ if (packageName == null || installType == INSTALL_EVENT_TYPE_UNSET) {
+ Slog.w(BIC_CALLBACK_HANDLER_TAG, "Package name or install type is "
+ + "unavailable, ignoring event");
+ return;
+ }
+ Slog.d(BIC_CALLBACK_HANDLER_TAG, "Detected new bic event for: " + packageName);
+ if (installType == BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL) {
+ PackageState packageState = LocalServices.getService(PackageManagerInternal.class)
+ .getPackageStateInternal(packageName);
+ if (packageState == null) {
+ Slog.w(TAG, "Package state is unavailable, ignoring the package "
+ + packageName);
+ return;
+ }
+ int mbaStatus = MBA_STATUS_NEW_INSTALL;
+ if (packageState.isUpdatedSystemApp()) {
+ mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
+ }
+ List<IBinaryTransparencyService.AppInfo> mbaInfo = mBicAppInfoHelper.collectAppInfo(
+ packageState, mbaStatus);
+ for (IBinaryTransparencyService.AppInfo appInfo : mbaInfo) {
+ mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+ }
+ } else if (installType
+ == BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL) {
+ IBinaryTransparencyService.AppInfo appInfo
+ = new IBinaryTransparencyService.AppInfo();
+ // since app is already uninstalled we won't be able to retrieve additional
+ // info on it.
+ appInfo.packageName = packageName;
+ appInfo.mbaStatus = MBA_STATUS_UNINSTALLED;
+ mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+ } else {
+ Slog.w(BIC_CALLBACK_HANDLER_TAG, "Unsupported BIC event: " + installType);
+ }
+ }
+
+ /**
+ * A wrapper of interface for{@link FrameworkStatsLog and ApkDigests}
+ * for easier testing
+ */
+ @VisibleForTesting
+ public interface IBicAppInfoHelper {
+
+ /**
+ * A wrapper of {@link FrameworkStatsLog}
+ *
+ * @param appInfo The app info of the changed MBA to be logged
+ */
+ public void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo);
+
+ /**
+ * A wrapper of {@link BinaryTransparencyServiceImpl}
+ *
+ * @param packageState The packageState provided retrieved from PackageManagerInternal
+ * @param mbaStatus The MBA status of the package
+ */
+ public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ PackageState packageState, int mbaStatus);
+ }
+ };
+
+ /**
* Called when the system service should publish a binder service using
* {@link #publishBinderService(String, IBinder).}
*/
@@ -1534,6 +1635,43 @@ public class BinaryTransparencyService extends SystemService {
}
}
+ private void registerBicCallback() {
+ if(!com.android.server.flags.Flags.optionalBackgroundInstallControl()) {
+ Slog.d(TAG, "BICS is disabled for this device, skipping registration.");
+ return;
+ }
+ IBackgroundInstallControlService iBics =
+ IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(
+ Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ if(iBics == null) {
+ Slog.e(TAG, "Failed to register BackgroundInstallControl callback, either "
+ + "background install control service does not exist or disabled on this "
+ + "build.");
+ return;
+ }
+ try {
+ iBics.registerBackgroundInstallCallback(
+ new BicCallbackHandler(
+ new BicCallbackHandler.IBicAppInfoHelper() {
+ @Override
+ public void writeAppInfoToLog(
+ IBinaryTransparencyService.AppInfo appInfo) {
+ mServiceImpl.writeAppInfoToLog(appInfo);
+ }
+
+ @Override
+ public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ PackageState packageState, int mbaStatus) {
+ return mServiceImpl.collectAppInfo(packageState, mbaStatus);
+ }
+ }
+ ));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register BackgroundInstallControl callback.");
+ }
+ }
+
private boolean isPackagePreloaded(String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
@@ -1575,8 +1713,12 @@ public class BinaryTransparencyService extends SystemService {
}
String packageName = data.getSchemeSpecificPart();
- // now we've got to check what package is this
- if (isPackagePreloaded(packageName) || isPackageAnApex(packageName)) {
+
+ boolean shouldMeasureMba =
+ !android.app.Flags.backgroundInstallControlCallbackApi()
+ && isPackagePreloaded(packageName);
+
+ if (shouldMeasureMba || isPackageAnApex(packageName)) {
Slog.d(TAG, packageName + " was updated. Scheduling measurement...");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
@@ -1596,6 +1738,10 @@ public class BinaryTransparencyService extends SystemService {
private void registerAllPackageUpdateObservers() {
registerApkAndNonStagedApexUpdateListener();
registerStagedApexUpdateObserver();
+ if (android.app.Flags.backgroundInstallControlCallbackApi()
+ && CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
+ registerBicCallback();
+ }
}
private String translateContentDigestAlgorithmIdToString(int algorithmId) {
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 16209b1dc3d3..4713a8d23b18 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -54,6 +54,7 @@ import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -407,9 +408,18 @@ public class BootReceiver extends BroadcastReceiver {
Slog.w(TAG, "Tombstone too large to add to DropBox: " + tombstone.toPath());
return;
}
- // Read the proto tombstone file as bytes.
- final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+ // Read the proto tombstone file as bytes.
+ // Previously used Files.readAllBytes() which internally creates a ThreadLocal BufferCache
+ // via ChannelInputStream that isn't properly released. Switched to
+ // FileInputStream.transferTo() which avoids the NIO channels completely,
+ // preventing the memory leak while maintaining the same functionality.
+ final byte[] tombstoneBytes;
+ try (FileInputStream fis = new FileInputStream(tombstone);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ fis.transferTo(baos);
+ tombstoneBytes = baos.toByteArray();
+ }
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index fb527c104946..2412b01ea8e2 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@ package com.android.server;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -35,6 +36,7 @@ import android.provider.Settings;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -259,11 +261,19 @@ final class DockObserver extends SystemService {
+ mReportedDockState);
final int previousDockState = mPreviousDockState;
mPreviousDockState = mReportedDockState;
- // Skip the dock intent if not yet provisioned.
+
final ContentResolver cr = getContext().getContentResolver();
- if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
- Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
- return;
+
+ /// If the allow dock rotation before provision is enabled then we allow rotation.
+ final Resources r = getContext().getResources();
+ final boolean allowDockBeforeProvision =
+ r.getBoolean(R.bool.config_allowDockBeforeProvision);
+ if (!allowDockBeforeProvision) {
+ // Skip the dock intent if not yet provisioned.
+ if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
+ Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
+ return;
+ }
}
// Pack up the values and broadcast them to everyone
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index fd512a64b32c..7474df2a91ca 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@ option java_package com.android.server
# replaces 27510 with a row per notification
27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
# a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1),(mute_reason|1)
# a notification was added to a autogroup
27533 notification_autogrouped (key|3)
# notification was removed from an autogroup
@@ -96,6 +96,8 @@ option java_package com.android.server
27535 notification_adjusted (key|3),(adjustment_type|3),(new_value|3)
# when a notification cancellation is prevented by the system
27536 notification_cancel_prevented (key|3)
+# when a summary notification is converted to a regular notification because of force autogrouping
+27537 notification_summary_converted (key|3)
# ---------------------------
# Watchdog.java
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
deleted file mode 100644
index 6a6aea49f6f2..000000000000
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
-import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-
-import android.Manifest;
-import android.annotation.MainThread;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.watchdog.ExplicitHealthCheckService;
-import android.service.watchdog.IExplicitHealthCheckService;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-
-// TODO(b/120598832): Add tests
-/**
- * Controls the connections with {@link ExplicitHealthCheckService}.
- */
-class ExplicitHealthCheckController {
- private static final String TAG = "ExplicitHealthCheckController";
- private final Object mLock = new Object();
- private final Context mContext;
-
- // Called everytime a package passes the health check, so the watchdog is notified of the
- // passing check. In practice, should never be null after it has been #setEnabled.
- // To prevent deadlocks between the controller and watchdog threads, we have
- // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
- // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
- @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
- // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
- // supporting health checks and update its internal state. In practice, should never be null
- // after it has been #setEnabled.
- // To prevent deadlocks between the controller and watchdog threads, we have
- // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
- // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
- @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer;
- // Called everytime we need to notify the watchdog to sync requests between itself and the
- // health check service. In practice, should never be null after it has been #setEnabled.
- // To prevent deadlocks between the controller and watchdog threads, we have
- // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
- // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
- @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
- // Actual binder object to the explicit health check service.
- @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
- // Connection to the explicit health check service, necessary to unbind.
- // We should only try to bind if mConnection is null, non-null indicates we
- // are connected or at least connecting.
- @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
- // Bind state of the explicit health check service.
- @GuardedBy("mLock") private boolean mEnabled;
-
- ExplicitHealthCheckController(Context context) {
- mContext = context;
- }
-
- /** Enables or disables explicit health checks. */
- public void setEnabled(boolean enabled) {
- synchronized (mLock) {
- Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
- mEnabled = enabled;
- }
- }
-
- /**
- * Sets callbacks to listen to important events from the controller.
- *
- * <p> Should be called once at initialization before any other calls to the controller to
- * ensure a happens-before relationship of the set parameters and visibility on other threads.
- */
- public void setCallbacks(Consumer<String> passedConsumer,
- Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
- synchronized (mLock) {
- if (mPassedConsumer != null || mSupportedConsumer != null
- || mNotifySyncRunnable != null) {
- Slog.wtf(TAG, "Resetting health check controller callbacks");
- }
-
- mPassedConsumer = Objects.requireNonNull(passedConsumer);
- mSupportedConsumer = Objects.requireNonNull(supportedConsumer);
- mNotifySyncRunnable = Objects.requireNonNull(notifySyncRunnable);
- }
- }
-
- /**
- * Calls the health check service to request or cancel packages based on
- * {@code newRequestedPackages}.
- *
- * <p> Supported packages in {@code newRequestedPackages} that have not been previously
- * requested will be requested while supported packages not in {@code newRequestedPackages}
- * but were previously requested will be cancelled.
- *
- * <p> This handles binding and unbinding to the health check service as required.
- *
- * <p> Note, calling this may modify {@code newRequestedPackages}.
- *
- * <p> Note, this method is not thread safe, all calls should be serialized.
- */
- public void syncRequests(Set<String> newRequestedPackages) {
- boolean enabled;
- synchronized (mLock) {
- enabled = mEnabled;
- }
-
- if (!enabled) {
- Slog.i(TAG, "Health checks disabled, no supported packages");
- // Call outside lock
- mSupportedConsumer.accept(Collections.emptyList());
- return;
- }
-
- getSupportedPackages(supportedPackageConfigs -> {
- // Notify the watchdog without lock held
- mSupportedConsumer.accept(supportedPackageConfigs);
- getRequestedPackages(previousRequestedPackages -> {
- synchronized (mLock) {
- // Hold lock so requests and cancellations are sent atomically.
- // It is important we don't mix requests from multiple threads.
-
- Set<String> supportedPackages = new ArraySet<>();
- for (PackageConfig config : supportedPackageConfigs) {
- supportedPackages.add(config.getPackageName());
- }
- // Note, this may modify newRequestedPackages
- newRequestedPackages.retainAll(supportedPackages);
-
- // Cancel packages no longer requested
- actOnDifference(previousRequestedPackages,
- newRequestedPackages, p -> cancel(p));
- // Request packages not yet requested
- actOnDifference(newRequestedPackages,
- previousRequestedPackages, p -> request(p));
-
- if (newRequestedPackages.isEmpty()) {
- Slog.i(TAG, "No more health check requests, unbinding...");
- unbindService();
- return;
- }
- }
- });
- });
- }
-
- private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
- Consumer<String> action) {
- Iterator<String> iterator = collection1.iterator();
- while (iterator.hasNext()) {
- String packageName = iterator.next();
- if (!collection2.contains(packageName)) {
- action.accept(packageName);
- }
- }
- }
-
- /**
- * Requests an explicit health check for {@code packageName}.
- * After this request, the callback registered on {@link #setCallbacks} can receive explicit
- * health check passed results.
- */
- private void request(String packageName) {
- synchronized (mLock) {
- if (!prepareServiceLocked("request health check for " + packageName)) {
- return;
- }
-
- Slog.i(TAG, "Requesting health check for package " + packageName);
- try {
- mRemoteService.request(packageName);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to request health check for package " + packageName, e);
- }
- }
- }
-
- /**
- * Cancels all explicit health checks for {@code packageName}.
- * After this request, the callback registered on {@link #setCallbacks} can no longer receive
- * explicit health check passed results.
- */
- private void cancel(String packageName) {
- synchronized (mLock) {
- if (!prepareServiceLocked("cancel health check for " + packageName)) {
- return;
- }
-
- Slog.i(TAG, "Cancelling health check for package " + packageName);
- try {
- mRemoteService.cancel(packageName);
- } catch (RemoteException e) {
- // Do nothing, if the service is down, when it comes up, we will sync requests,
- // if there's some other error, retrying wouldn't fix anyways.
- Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
- }
- }
- }
-
- /**
- * Returns the packages that we can request explicit health checks for.
- * The packages will be returned to the {@code consumer}.
- */
- private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) {
- synchronized (mLock) {
- if (!prepareServiceLocked("get health check supported packages")) {
- return;
- }
-
- Slog.d(TAG, "Getting health check supported packages");
- try {
- mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
- List<PackageConfig> packages =
- result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, android.service.watchdog.ExplicitHealthCheckService.PackageConfig.class);
- Slog.i(TAG, "Explicit health check supported packages " + packages);
- consumer.accept(packages);
- }));
- } catch (RemoteException e) {
- // Request failed, treat as if all observed packages are supported, if any packages
- // expire during this period, we may incorrectly treat it as failing health checks
- // even if we don't support health checks for the package.
- Slog.w(TAG, "Failed to get health check supported packages", e);
- }
- }
- }
-
- /**
- * Returns the packages for which health checks are currently in progress.
- * The packages will be returned to the {@code consumer}.
- */
- private void getRequestedPackages(Consumer<List<String>> consumer) {
- synchronized (mLock) {
- if (!prepareServiceLocked("get health check requested packages")) {
- return;
- }
-
- Slog.d(TAG, "Getting health check requested packages");
- try {
- mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
- List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
- Slog.i(TAG, "Explicit health check requested packages " + packages);
- consumer.accept(packages);
- }));
- } catch (RemoteException e) {
- // Request failed, treat as if we haven't requested any packages, if any packages
- // were actually requested, they will not be cancelled now. May be cancelled later
- Slog.w(TAG, "Failed to get health check requested packages", e);
- }
- }
- }
-
- /**
- * Binds to the explicit health check service if the controller is enabled and
- * not already bound.
- */
- private void bindService() {
- synchronized (mLock) {
- if (!mEnabled || mConnection != null || mRemoteService != null) {
- if (!mEnabled) {
- Slog.i(TAG, "Not binding to service, service disabled");
- } else if (mRemoteService != null) {
- Slog.i(TAG, "Not binding to service, service already connected");
- } else {
- Slog.i(TAG, "Not binding to service, service already connecting");
- }
- return;
- }
- ComponentName component = getServiceComponentNameLocked();
- if (component == null) {
- Slog.wtf(TAG, "Explicit health check service not found");
- return;
- }
-
- Intent intent = new Intent();
- intent.setComponent(component);
- mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Slog.i(TAG, "Explicit health check service is connected " + name);
- initState(service);
- }
-
- @Override
- @MainThread
- public void onServiceDisconnected(ComponentName name) {
- // Service crashed or process was killed, #onServiceConnected will be called.
- // Don't need to re-bind.
- Slog.i(TAG, "Explicit health check service is disconnected " + name);
- synchronized (mLock) {
- mRemoteService = null;
- }
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- // Application hosting service probably got updated
- // Need to re-bind.
- Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
- unbindService();
- bindService();
- }
-
- @Override
- public void onNullBinding(ComponentName name) {
- // Should never happen. Service returned null from #onBind.
- Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
- }
- };
-
- mContext.bindServiceAsUser(intent, mConnection,
- Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
- Slog.i(TAG, "Explicit health check service is bound");
- }
- }
-
- /** Unbinds the explicit health check service. */
- private void unbindService() {
- synchronized (mLock) {
- if (mRemoteService != null) {
- mContext.unbindService(mConnection);
- mRemoteService = null;
- mConnection = null;
- }
- Slog.i(TAG, "Explicit health check service is unbound");
- }
- }
-
- @GuardedBy("mLock")
- @Nullable
- private ServiceInfo getServiceInfoLocked() {
- if (refactorCrashrecovery()) {
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
- | PackageManager.MATCH_SYSTEM_ONLY);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
- }
- return resolveInfo.serviceInfo;
- } else {
- final String packageName =
- mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
- if (packageName == null) {
- Slog.w(TAG, "no external services package!");
- return null;
- }
-
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- intent.setPackage(packageName);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
- }
- return resolveInfo.serviceInfo;
- }
- }
-
- @GuardedBy("mLock")
- @Nullable
- private ComponentName getServiceComponentNameLocked() {
- final ServiceInfo serviceInfo = getServiceInfoLocked();
- if (serviceInfo == null) {
- return null;
- }
-
- final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
- if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
- .equals(serviceInfo.permission)) {
- Slog.w(TAG, name.flattenToShortString() + " does not require permission "
- + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
- return null;
- }
- return name;
- }
-
- private void initState(IBinder service) {
- synchronized (mLock) {
- if (!mEnabled) {
- Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
- // Very unlikely, but we disabled the service after binding but before we connected
- unbindService();
- return;
- }
- mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
- try {
- mRemoteService.setCallback(new RemoteCallback(result -> {
- String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
- if (!TextUtils.isEmpty(packageName)) {
- if (mPassedConsumer == null) {
- Slog.wtf(TAG, "Health check passed for package " + packageName
- + "but no consumer registered.");
- } else {
- // Call without lock held
- mPassedConsumer.accept(packageName);
- }
- } else {
- Slog.wtf(TAG, "Empty package passed explicit health check?");
- }
- }));
- Slog.i(TAG, "Service initialized, syncing requests");
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Could not setCallback on explicit health check service");
- }
- }
- // Calling outside lock
- mNotifySyncRunnable.run();
- }
-
- /**
- * Prepares the health check service to receive requests.
- *
- * @return {@code true} if it is ready and we can proceed with a request,
- * {@code false} otherwise. If it is not ready, and the service is enabled,
- * we will bind and the request should be automatically attempted later.
- */
- @GuardedBy("mLock")
- private boolean prepareServiceLocked(String action) {
- if (mRemoteService != null && mEnabled) {
- return true;
- }
- Slog.i(TAG, "Service not ready to " + action
- + (mEnabled ? ". Binding..." : ". Disabled"));
- if (mEnabled) {
- bindService();
- }
- return false;
- }
-}
diff --git a/services/core/java/com/android/server/FgThread.java b/services/core/java/com/android/server/FgThread.java
deleted file mode 100644
index b4b6e3f1c3c1..000000000000
--- a/services/core/java/com/android/server/FgThread.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
-import android.os.Trace;
-
-import java.util.concurrent.Executor;
-
-/**
- * Shared singleton foreground thread for the system. This is a thread for regular
- * foreground service operations, which shouldn't be blocked by anything running in
- * the background. In particular, the shared background thread could be doing
- * relatively long-running operations like saving state to disk (in addition to
- * simply being a background priority), which can cause operations scheduled on it
- * to be delayed for a user-noticeable amount of time.
- */
-public final class FgThread extends ServiceThread {
- private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
- private static final long SLOW_DELIVERY_THRESHOLD_MS = 200;
-
- private static FgThread sInstance;
- private static Handler sHandler;
- private static HandlerExecutor sHandlerExecutor;
-
- private FgThread() {
- super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
- }
-
- private static void ensureThreadLocked() {
- if (sInstance == null) {
- sInstance = new FgThread();
- sInstance.start();
- final Looper looper = sInstance.getLooper();
- looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
- looper.setSlowLogThresholdMs(
- SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
- sHandler = makeSharedHandler(sInstance.getLooper());
- sHandlerExecutor = new HandlerExecutor(sHandler);
- }
- }
-
- public static FgThread get() {
- synchronized (FgThread.class) {
- ensureThreadLocked();
- return sInstance;
- }
- }
-
- public static Handler getHandler() {
- synchronized (FgThread.class) {
- ensureThreadLocked();
- return sHandler;
- }
- }
-
- public static Executor getExecutor() {
- synchronized (FgThread.class) {
- ensureThreadLocked();
- return sHandlerExecutor;
- }
- }
-}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
deleted file mode 100644
index 32be643382aa..000000000000
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ /dev/null
@@ -1,2083 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.content.Intent.ACTION_REBOOT;
-import static android.content.Intent.ACTION_SHUTDOWN;
-import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
-import android.net.ConnectivityModuleConnector;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.provider.DeviceConfig;
-import android.sysprop.CrashRecoveryProperties;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.IndentingPrintWriter;
-import android.util.LongArrayQueue;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.XmlUtils;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Monitors the health of packages on the system and notifies interested observers when packages
- * fail. On failure, the registered observer with the least user impacting mitigation will
- * be notified.
- */
-public class PackageWatchdog {
- private static final String TAG = "PackageWatchdog";
-
- static final String PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS =
- "watchdog_trigger_failure_duration_millis";
- static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
- "watchdog_trigger_failure_count";
- static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
- "watchdog_explicit_health_check_enabled";
-
- // TODO: make the following values configurable via DeviceConfig
- private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
- TimeUnit.SECONDS.toMillis(30);
- private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
-
-
- public static final int FAILURE_REASON_UNKNOWN = 0;
- public static final int FAILURE_REASON_NATIVE_CRASH = 1;
- public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
- public static final int FAILURE_REASON_APP_CRASH = 3;
- public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
- public static final int FAILURE_REASON_BOOT_LOOP = 5;
-
- @IntDef(prefix = { "FAILURE_REASON_" }, value = {
- FAILURE_REASON_UNKNOWN,
- FAILURE_REASON_NATIVE_CRASH,
- FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
- FAILURE_REASON_APP_CRASH,
- FAILURE_REASON_APP_NOT_RESPONDING,
- FAILURE_REASON_BOOT_LOOP
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface FailureReasons {}
-
- // Duration to count package failures before it resets to 0
- @VisibleForTesting
- static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
- (int) TimeUnit.MINUTES.toMillis(1);
- // Number of package failures within the duration above before we notify observers
- @VisibleForTesting
- static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
- @VisibleForTesting
- static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
- // Sliding window for tracking how many mitigation calls were made for a package.
- @VisibleForTesting
- static final long DEFAULT_DEESCALATION_WINDOW_MS = TimeUnit.HOURS.toMillis(1);
- // Whether explicit health checks are enabled or not
- private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
-
- @VisibleForTesting
- static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
-
- static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
-
- // Time needed to apply mitigation
- private static final String MITIGATION_WINDOW_MS =
- "persist.device_config.configuration.mitigation_window_ms";
- @VisibleForTesting
- static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5);
-
- // Threshold level at which or above user might experience significant disruption.
- private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
- "persist.device_config.configuration.major_user_impact_level_threshold";
- private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
-
- // Comma separated list of all packages exempt from user impact level threshold. If a package
- // in the list is crash looping, all the mitigations including factory reset will be performed.
- private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
- "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
-
- // Comma separated list of default packages exempt from user impact level threshold.
- private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
- "com.android.systemui";
-
- private long mNumberOfNativeCrashPollsRemaining;
-
- private static final int DB_VERSION = 1;
- private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
- private static final String TAG_PACKAGE = "package";
- private static final String TAG_OBSERVER = "observer";
- private static final String ATTR_VERSION = "version";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_DURATION = "duration";
- private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
- private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
- private static final String ATTR_MITIGATION_CALLS = "mitigation-calls";
- private static final String ATTR_MITIGATION_COUNT = "mitigation-count";
-
- // A file containing information about the current mitigation count in the case of a boot loop.
- // This allows boot loop information to persist in the case of an fs-checkpoint being
- // aborted.
- private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
-
- @GuardedBy("PackageWatchdog.class")
- private static PackageWatchdog sPackageWatchdog;
-
- private final Object mLock = new Object();
- // System server context
- private final Context mContext;
- // Handler to run short running tasks
- private final Handler mShortTaskHandler;
- // Handler for processing IO and long running tasks
- private final Handler mLongTaskHandler;
- // Contains (observer-name -> observer-handle) that have ever been registered from
- // previous boots. Observers with all packages expired are periodically pruned.
- // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
- @GuardedBy("mLock")
- private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
- // File containing the XML data of monitored packages /data/system/package-watchdog.xml
- private final AtomicFile mPolicyFile;
- private final ExplicitHealthCheckController mHealthCheckController;
- private final ConnectivityModuleConnector mConnectivityModuleConnector;
- private final Runnable mSyncRequests = this::syncRequests;
- private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
- private final Runnable mSaveToFile = this::saveToFile;
- private final SystemClock mSystemClock;
- private final BootThreshold mBootThreshold;
- private final DeviceConfig.OnPropertiesChangedListener
- mOnPropertyChangedListener = this::onPropertyChanged;
-
- private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
-
- // The set of packages that have been synced with the ExplicitHealthCheckController
- @GuardedBy("mLock")
- private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
- @GuardedBy("mLock")
- private boolean mIsPackagesReady;
- // Flag to control whether explicit health checks are supported or not
- @GuardedBy("mLock")
- private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED;
- @GuardedBy("mLock")
- private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
- @GuardedBy("mLock")
- private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
- // SystemClock#uptimeMillis when we last executed #syncState
- // 0 if no prune is scheduled.
- @GuardedBy("mLock")
- private long mUptimeAtLastStateSync;
- // If true, sync explicit health check packages with the ExplicitHealthCheckController.
- @GuardedBy("mLock")
- private boolean mSyncRequired = false;
-
- @GuardedBy("mLock")
- private long mLastMitigation = -1000000;
-
- @FunctionalInterface
- @VisibleForTesting
- interface SystemClock {
- long uptimeMillis();
- }
-
- private PackageWatchdog(Context context) {
- // Needs to be constructed inline
- this(context, new AtomicFile(
- new File(new File(Environment.getDataDirectory(), "system"),
- "package-watchdog.xml")),
- new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
- new ExplicitHealthCheckController(context),
- ConnectivityModuleConnector.getInstance(),
- android.os.SystemClock::uptimeMillis);
- }
-
- /**
- * Creates a PackageWatchdog that allows injecting dependencies.
- */
- @VisibleForTesting
- PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
- Handler longTaskHandler, ExplicitHealthCheckController controller,
- ConnectivityModuleConnector connectivityModuleConnector, SystemClock clock) {
- mContext = context;
- mPolicyFile = policyFile;
- mShortTaskHandler = shortTaskHandler;
- mLongTaskHandler = longTaskHandler;
- mHealthCheckController = controller;
- mConnectivityModuleConnector = connectivityModuleConnector;
- mSystemClock = clock;
- mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
- mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
-
- loadFromFile();
- sPackageWatchdog = this;
- }
-
- /** Creates or gets singleton instance of PackageWatchdog. */
- public static PackageWatchdog getInstance(Context context) {
- synchronized (PackageWatchdog.class) {
- if (sPackageWatchdog == null) {
- new PackageWatchdog(context);
- }
- return sPackageWatchdog;
- }
- }
-
- /**
- * Called during boot to notify when packages are ready on the device so we can start
- * binding.
- */
- public void onPackagesReady() {
- synchronized (mLock) {
- mIsPackagesReady = true;
- mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
- packages -> onSupportedPackages(packages),
- this::onSyncRequestNotified);
- setPropertyChangedListenerLocked();
- updateConfigs();
- if (!Flags.refactorCrashrecovery()) {
- registerConnectivityModuleHealthListener();
- }
- }
- }
-
- /**
- * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
- * this observer if it does not already exist.
- *
- * <p>Observers are expected to call this on boot. It does not specify any packages but
- * it will resume observing any packages requested from a previous boot.
- */
- public void registerHealthObserver(PackageHealthObserver observer) {
- synchronized (mLock) {
- ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
- if (internalObserver != null) {
- internalObserver.registeredObserver = observer;
- } else {
- internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
- new ArrayList<>());
- internalObserver.registeredObserver = observer;
- mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
- syncState("added new observer");
- }
- }
- }
-
- /**
- * Starts observing the health of the {@code packages} for {@code observer} and notifies
- * {@code observer} of any package failures within the monitoring duration.
- *
- * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
- * duration if {@link #onHealthCheckPassed} was never called,
- * {@link PackageHealthObserver#execute} will be called as if the package failed.
- *
- * <p>If {@code observer} is already monitoring a package in {@code packageNames},
- * the monitoring window of that package will be reset to {@code durationMs} and the health
- * check state will be reset to a default depending on if the package is contained in
- * {@link mPackagesWithExplicitHealthCheckEnabled}.
- *
- * <p>If {@code packageNames} is empty, this will be a no-op.
- *
- * <p>If {@code durationMs} is less than 1, a default monitoring duration
- * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
- */
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- long durationMs) {
- if (packageNames.isEmpty()) {
- Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
- return;
- }
- if (durationMs < 1) {
- Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
- + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
- durationMs = DEFAULT_OBSERVING_DURATION_MS;
- }
-
- List<MonitoredPackage> packages = new ArrayList<>();
- for (int i = 0; i < packageNames.size(); i++) {
- // Health checks not available yet so health check state will start INACTIVE
- MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), durationMs, false);
- if (pkg != null) {
- packages.add(pkg);
- } else {
- Slog.w(TAG, "Failed to create MonitoredPackage for pkg=" + packageNames.get(i));
- }
- }
-
- if (packages.isEmpty()) {
- return;
- }
-
- // Sync before we add the new packages to the observers. This will #pruneObservers,
- // causing any elapsed time to be deducted from all existing packages before we add new
- // packages. This maintains the invariant that the elapsed time for ALL (new and existing)
- // packages is the same.
- mLongTaskHandler.post(() -> {
- syncState("observing new packages");
-
- synchronized (mLock) {
- ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
- if (oldObserver == null) {
- Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
- + "of packages " + packageNames);
- mAllObservers.put(observer.getUniqueIdentifier(),
- new ObserverInternal(observer.getUniqueIdentifier(), packages));
- } else {
- Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
- + "packages to monitor " + packageNames);
- oldObserver.updatePackagesLocked(packages);
- }
- }
-
- // Register observer in case not already registered
- registerHealthObserver(observer);
-
- // Sync after we add the new packages to the observers. We may have received packges
- // requiring an earlier schedule than we are currently scheduled for.
- syncState("updated observers");
- });
-
- }
-
- /**
- * Unregisters {@code observer} from listening to package failure.
- * Additionally, this stops observing any packages that may have previously been observed
- * even from a previous boot.
- */
- public void unregisterHealthObserver(PackageHealthObserver observer) {
- mLongTaskHandler.post(() -> {
- synchronized (mLock) {
- mAllObservers.remove(observer.getUniqueIdentifier());
- }
- syncState("unregistering observer: " + observer.getUniqueIdentifier());
- });
- }
-
- /**
- * Called when a process fails due to a crash, ANR or explicit health check.
- *
- * <p>For each package contained in the process, one registered observer with the least user
- * impact will be notified for mitigation.
- *
- * <p>This method could be called frequently if there is a severe problem on the device.
- */
- public void onPackageFailure(List<VersionedPackage> packages,
- @FailureReasons int failureReason) {
- if (packages == null) {
- Slog.w(TAG, "Could not resolve a list of failing packages");
- return;
- }
- synchronized (mLock) {
- final long now = mSystemClock.uptimeMillis();
- if (Flags.recoverabilityDetection()) {
- if (now >= mLastMitigation
- && (now - mLastMitigation) < getMitigationWindowMs()) {
- Slog.i(TAG, "Skipping onPackageFailure mitigation");
- return;
- }
- }
- }
- mLongTaskHandler.post(() -> {
- synchronized (mLock) {
- if (mAllObservers.isEmpty()) {
- return;
- }
- boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
- || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
- if (requiresImmediateAction) {
- handleFailureImmediately(packages, failureReason);
- } else {
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- VersionedPackage versionedPackage = packages.get(pIndex);
- // Observer that will receive failure for versionedPackage
- PackageHealthObserver currentObserverToNotify = null;
- int currentObserverImpact = Integer.MAX_VALUE;
- MonitoredPackage currentMonitoredPackage = null;
-
- // Find observer with least user impact
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- ObserverInternal observer = mAllObservers.valueAt(oIndex);
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null
- && observer.onPackageFailureLocked(
- versionedPackage.getPackageName())) {
- MonitoredPackage p = observer.getMonitoredPackage(
- versionedPackage.getPackageName());
- int mitigationCount = 1;
- if (p != null) {
- mitigationCount = p.getMitigationCountLocked() + 1;
- }
- int impact = registeredObserver.onHealthCheckFailed(
- versionedPackage, failureReason, mitigationCount);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
- && impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverImpact = impact;
- currentMonitoredPackage = p;
- }
- }
- }
-
- // Execute action with least user impact
- if (currentObserverToNotify != null) {
- int mitigationCount = 1;
- if (currentMonitoredPackage != null) {
- currentMonitoredPackage.noteMitigationCallLocked();
- mitigationCount =
- currentMonitoredPackage.getMitigationCountLocked();
- }
- if (Flags.recoverabilityDetection()) {
- maybeExecute(currentObserverToNotify, versionedPackage,
- failureReason, currentObserverImpact, mitigationCount);
- } else {
- currentObserverToNotify.execute(versionedPackage,
- failureReason, mitigationCount);
- }
- }
- }
- }
- }
- });
- }
-
- /**
- * For native crashes or explicit health check failures, call directly into each observer to
- * mitigate the error without going through failure threshold logic.
- */
- private void handleFailureImmediately(List<VersionedPackage> packages,
- @FailureReasons int failureReason) {
- VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
- PackageHealthObserver currentObserverToNotify = null;
- int currentObserverImpact = Integer.MAX_VALUE;
- for (ObserverInternal observer: mAllObservers.values()) {
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null) {
- int impact = registeredObserver.onHealthCheckFailed(
- failingPackage, failureReason, 1);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
- && impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverImpact = impact;
- }
- }
- }
- if (currentObserverToNotify != null) {
- if (Flags.recoverabilityDetection()) {
- maybeExecute(currentObserverToNotify, failingPackage, failureReason,
- currentObserverImpact, /*mitigationCount=*/ 1);
- } else {
- currentObserverToNotify.execute(failingPackage, failureReason, 1);
- }
- }
- }
-
- private void maybeExecute(PackageHealthObserver currentObserverToNotify,
- VersionedPackage versionedPackage,
- @FailureReasons int failureReason,
- int currentObserverImpact,
- int mitigationCount) {
- if (allowMitigations(currentObserverImpact, versionedPackage)) {
- synchronized (mLock) {
- mLastMitigation = mSystemClock.uptimeMillis();
- }
- currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount);
- }
- }
-
- private boolean allowMitigations(int currentObserverImpact,
- VersionedPackage versionedPackage) {
- return currentObserverImpact < getUserImpactLevelLimit()
- || getPackagesExemptFromImpactLevelThreshold().contains(
- versionedPackage.getPackageName());
- }
-
- private long getMitigationWindowMs() {
- return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
- }
-
-
- /**
- * Called when the system server boots. If the system server is detected to be in a boot loop,
- * query each observer and perform the mitigation action with the lowest user impact.
- *
- * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
- * are not counted in bootloop.
- */
- @SuppressWarnings("GuardedBy")
- public void noteBoot() {
- synchronized (mLock) {
- // if boot count has reached threshold, start mitigation.
- // We wait until threshold number of restarts only for the first time. Perform
- // mitigations for every restart after that.
- boolean mitigate = mBootThreshold.incrementAndTest();
- if (mitigate) {
- if (!Flags.recoverabilityDetection()) {
- mBootThreshold.reset();
- }
- int mitigationCount = mBootThreshold.getMitigationCount() + 1;
- PackageHealthObserver currentObserverToNotify = null;
- ObserverInternal currentObserverInternal = null;
- int currentObserverImpact = Integer.MAX_VALUE;
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null) {
- int impact = Flags.recoverabilityDetection()
- ? registeredObserver.onBootLoop(
- observer.getBootMitigationCount() + 1)
- : registeredObserver.onBootLoop(mitigationCount);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
- && impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverInternal = observer;
- currentObserverImpact = impact;
- }
- }
- }
- if (currentObserverToNotify != null) {
- if (Flags.recoverabilityDetection()) {
- int currentObserverMitigationCount =
- currentObserverInternal.getBootMitigationCount() + 1;
- currentObserverInternal.setBootMitigationCount(
- currentObserverMitigationCount);
- saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- currentObserverToNotify.executeBootLoopMitigation(
- currentObserverMitigationCount);
- } else {
- mBootThreshold.setMitigationCount(mitigationCount);
- mBootThreshold.saveMitigationCountToMetadata();
- currentObserverToNotify.executeBootLoopMitigation(mitigationCount);
- }
- }
- }
- }
- }
-
- // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
- // avoid holding lock?
- // This currently adds about 7ms extra to shutdown thread
- /** Writes the package information to file during shutdown. */
- public void writeNow() {
- synchronized (mLock) {
- // Must only run synchronous tasks as this runs on the ShutdownThread and no other
- // thread is guaranteed to run during shutdown.
- if (!mAllObservers.isEmpty()) {
- mLongTaskHandler.removeCallbacks(mSaveToFile);
- pruneObserversLocked();
- saveToFile();
- Slog.i(TAG, "Last write to update package durations");
- }
- }
- }
-
- /**
- * Enables or disables explicit health checks.
- * <p> If explicit health checks are enabled, the health check service is started.
- * <p> If explicit health checks are disabled, pending explicit health check requests are
- * passed and the health check service is stopped.
- */
- private void setExplicitHealthCheckEnabled(boolean enabled) {
- synchronized (mLock) {
- mIsHealthCheckEnabled = enabled;
- mHealthCheckController.setEnabled(enabled);
- mSyncRequired = true;
- // Prune to update internal state whenever health check is enabled/disabled
- syncState("health check state " + (enabled ? "enabled" : "disabled"));
- }
- }
-
- /**
- * This method should be only called on mShortTaskHandler, since it modifies
- * {@link #mNumberOfNativeCrashPollsRemaining}.
- */
- private void checkAndMitigateNativeCrashes() {
- mNumberOfNativeCrashPollsRemaining--;
- // Check if native watchdog reported a crash
- if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- // We rollback all available low impact rollbacks when crash is unattributable
- onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
- // we stop polling after an attempt to execute rollback, regardless of whether the
- // attempt succeeds or not
- } else {
- if (mNumberOfNativeCrashPollsRemaining > 0) {
- mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
- NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
- }
- }
- }
-
- /**
- * Since this method can eventually trigger a rollback, it should be called
- * only once boot has completed {@code onBootCompleted} and not earlier, because the install
- * session must be entirely completed before we try to rollback.
- */
- public void scheduleCheckAndMitigateNativeCrashes() {
- Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
- + "and mitigate native crashes");
- mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
- }
-
- private int getUserImpactLevelLimit() {
- return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD,
- DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
- }
-
- private Set<String> getPackagesExemptFromImpactLevelThreshold() {
- if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
- String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
- DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
- return Set.of(packageNames.split("\\s*,\\s*"));
- }
- return mPackagesExemptFromImpactLevelThreshold;
- }
-
- /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
- @Retention(SOURCE)
- @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_75,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_80,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
- public @interface PackageHealthObserverImpact {
- /** No action to take. */
- int USER_IMPACT_LEVEL_0 = 0;
- /* Action has low user impact, user of a device will barely notice. */
- int USER_IMPACT_LEVEL_10 = 10;
- /* Actions having medium user impact, user of a device will likely notice. */
- int USER_IMPACT_LEVEL_20 = 20;
- int USER_IMPACT_LEVEL_30 = 30;
- int USER_IMPACT_LEVEL_40 = 40;
- int USER_IMPACT_LEVEL_50 = 50;
- int USER_IMPACT_LEVEL_70 = 70;
- /* Action has high user impact, a last resort, user of a device will be very frustrated. */
- int USER_IMPACT_LEVEL_71 = 71;
- int USER_IMPACT_LEVEL_75 = 75;
- int USER_IMPACT_LEVEL_80 = 80;
- int USER_IMPACT_LEVEL_90 = 90;
- int USER_IMPACT_LEVEL_100 = 100;
- }
-
- /** Register instances of this interface to receive notifications on package failure. */
- public interface PackageHealthObserver {
- /**
- * Called when health check fails for the {@code versionedPackage}.
- *
- * @param versionedPackage the package that is failing. This may be null if a native
- * service is crashing.
- * @param failureReason the type of failure that is occurring.
- * @param mitigationCount the number of times mitigation has been called for this package
- * (including this time).
- *
- *
- * @return any one of {@link PackageHealthObserverImpact} to express the impact
- * to the user on {@link #execute}
- */
- @PackageHealthObserverImpact int onHealthCheckFailed(
- @Nullable VersionedPackage versionedPackage,
- @FailureReasons int failureReason,
- int mitigationCount);
-
- /**
- * Executes mitigation for {@link #onHealthCheckFailed}.
- *
- * @param versionedPackage the package that is failing. This may be null if a native
- * service is crashing.
- * @param failureReason the type of failure that is occurring.
- * @param mitigationCount the number of times mitigation has been called for this package
- * (including this time).
- * @return {@code true} if action was executed successfully, {@code false} otherwise
- */
- boolean execute(@Nullable VersionedPackage versionedPackage,
- @FailureReasons int failureReason, int mitigationCount);
-
-
- /**
- * Called when the system server has booted several times within a window of time, defined
- * by {@link #mBootThreshold}
- *
- * @param mitigationCount the number of times mitigation has been attempted for this
- * boot loop (including this time).
- */
- default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
-
- /**
- * Executes mitigation for {@link #onBootLoop}
- * @param mitigationCount the number of times mitigation has been attempted for this
- * boot loop (including this time).
- */
- default boolean executeBootLoopMitigation(int mitigationCount) {
- return false;
- }
-
- // TODO(b/120598832): Ensure uniqueness?
- /**
- * Identifier for the observer, should not change across device updates otherwise the
- * watchdog may drop observing packages with the old name.
- */
- String getUniqueIdentifier();
-
- /**
- * An observer will not be pruned if this is set, even if the observer is not explicitly
- * monitoring any packages.
- */
- default boolean isPersistent() {
- return false;
- }
-
- /**
- * Returns {@code true} if this observer wishes to observe the given package, {@code false}
- * otherwise
- *
- * <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
- */
- default boolean mayObservePackage(String packageName) {
- return false;
- }
- }
-
- @VisibleForTesting
- long getTriggerFailureCount() {
- synchronized (mLock) {
- return mTriggerFailureCount;
- }
- }
-
- @VisibleForTesting
- long getTriggerFailureDurationMs() {
- synchronized (mLock) {
- return mTriggerFailureDurationMs;
- }
- }
-
- /**
- * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}.
- */
- private void syncRequestsAsync() {
- mShortTaskHandler.removeCallbacks(mSyncRequests);
- mShortTaskHandler.post(mSyncRequests);
- }
-
- /**
- * Syncs health check requests with the {@link ExplicitHealthCheckController}.
- * Calls to this must be serialized.
- *
- * @see #syncRequestsAsync
- */
- private void syncRequests() {
- boolean syncRequired = false;
- synchronized (mLock) {
- if (mIsPackagesReady) {
- Set<String> packages = getPackagesPendingHealthChecksLocked();
- if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages)
- || packages.isEmpty()) {
- syncRequired = true;
- mRequestedHealthCheckPackages = packages;
- }
- } // else, we will sync requests when packages become ready
- }
-
- // Call outside lock to avoid holding lock when calling into the controller.
- if (syncRequired) {
- Slog.i(TAG, "Syncing health check requests for packages: "
- + mRequestedHealthCheckPackages);
- mHealthCheckController.syncRequests(mRequestedHealthCheckPackages);
- mSyncRequired = false;
- }
- }
-
- /**
- * Updates the observers monitoring {@code packageName} that explicit health check has passed.
- *
- * <p> This update is strictly for registered observers at the time of the call
- * Observers that register after this signal will have no knowledge of prior signals and will
- * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
- *
- * <p> {@code packageName} can still be considered failed if reported by
- * {@link #onPackageFailureLocked} before the package expires.
- *
- * <p> Triggered by components outside the system server when they are fully functional after an
- * update.
- */
- private void onHealthCheckPassed(String packageName) {
- Slog.i(TAG, "Health check passed for package: " + packageName);
- boolean isStateChanged = false;
-
- synchronized (mLock) {
- for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
- ObserverInternal observer = mAllObservers.valueAt(observerIdx);
- MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName);
-
- if (monitoredPackage != null) {
- int oldState = monitoredPackage.getHealthCheckStateLocked();
- int newState = monitoredPackage.tryPassHealthCheckLocked();
- isStateChanged |= oldState != newState;
- }
- }
- }
-
- if (isStateChanged) {
- syncState("health check passed for " + packageName);
- }
- }
-
- private void onSupportedPackages(List<PackageConfig> supportedPackages) {
- boolean isStateChanged = false;
-
- Map<String, Long> supportedPackageTimeouts = new ArrayMap<>();
- Iterator<PackageConfig> it = supportedPackages.iterator();
- while (it.hasNext()) {
- PackageConfig info = it.next();
- supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
- }
-
- synchronized (mLock) {
- Slog.d(TAG, "Received supported packages " + supportedPackages);
- Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
- while (oit.hasNext()) {
- Iterator<MonitoredPackage> pit = oit.next().getMonitoredPackages()
- .values().iterator();
- while (pit.hasNext()) {
- MonitoredPackage monitoredPackage = pit.next();
- String packageName = monitoredPackage.getName();
- int oldState = monitoredPackage.getHealthCheckStateLocked();
- int newState;
-
- if (supportedPackageTimeouts.containsKey(packageName)) {
- // Supported packages become ACTIVE if currently INACTIVE
- newState = monitoredPackage.setHealthCheckActiveLocked(
- supportedPackageTimeouts.get(packageName));
- } else {
- // Unsupported packages are marked as PASSED unless already FAILED
- newState = monitoredPackage.tryPassHealthCheckLocked();
- }
- isStateChanged |= oldState != newState;
- }
- }
- }
-
- if (isStateChanged) {
- syncState("updated health check supported packages " + supportedPackages);
- }
- }
-
- private void onSyncRequestNotified() {
- synchronized (mLock) {
- mSyncRequired = true;
- syncRequestsAsync();
- }
- }
-
- @GuardedBy("mLock")
- private Set<String> getPackagesPendingHealthChecksLocked() {
- Set<String> packages = new ArraySet<>();
- Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
- while (oit.hasNext()) {
- ObserverInternal observer = oit.next();
- Iterator<MonitoredPackage> pit =
- observer.getMonitoredPackages().values().iterator();
- while (pit.hasNext()) {
- MonitoredPackage monitoredPackage = pit.next();
- String packageName = monitoredPackage.getName();
- if (monitoredPackage.isPendingHealthChecksLocked()) {
- packages.add(packageName);
- }
- }
- }
- return packages;
- }
-
- /**
- * Syncs the state of the observers.
- *
- * <p> Prunes all observers, saves new state to disk, syncs health check requests with the
- * health check service and schedules the next state sync.
- */
- private void syncState(String reason) {
- synchronized (mLock) {
- Slog.i(TAG, "Syncing state, reason: " + reason);
- pruneObserversLocked();
-
- saveToFileAsync();
- syncRequestsAsync();
-
- // Done syncing state, schedule the next state sync
- scheduleNextSyncStateLocked();
- }
- }
-
- private void syncStateWithScheduledReason() {
- syncState("scheduled");
- }
-
- @GuardedBy("mLock")
- private void scheduleNextSyncStateLocked() {
- long durationMs = getNextStateSyncMillisLocked();
- mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason);
- if (durationMs == Long.MAX_VALUE) {
- Slog.i(TAG, "Cancelling state sync, nothing to sync");
- mUptimeAtLastStateSync = 0;
- } else {
- mUptimeAtLastStateSync = mSystemClock.uptimeMillis();
- mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs);
- }
- }
-
- /**
- * Returns the next duration in millis to sync the watchdog state.
- *
- * @returns Long#MAX_VALUE if there are no observed packages.
- */
- @GuardedBy("mLock")
- private long getNextStateSyncMillisLocked() {
- long shortestDurationMs = Long.MAX_VALUE;
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex)
- .getMonitoredPackages();
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- MonitoredPackage mp = packages.valueAt(pIndex);
- long duration = mp.getShortestScheduleDurationMsLocked();
- if (duration < shortestDurationMs) {
- shortestDurationMs = duration;
- }
- }
- }
- return shortestDurationMs;
- }
-
- /**
- * Removes {@code elapsedMs} milliseconds from all durations on monitored packages
- * and updates other internal state.
- */
- @GuardedBy("mLock")
- private void pruneObserversLocked() {
- long elapsedMs = mUptimeAtLastStateSync == 0
- ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
- if (elapsedMs <= 0) {
- Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
- return;
- }
-
- Iterator<ObserverInternal> it = mAllObservers.values().iterator();
- while (it.hasNext()) {
- ObserverInternal observer = it.next();
- Set<MonitoredPackage> failedPackages =
- observer.prunePackagesLocked(elapsedMs);
- if (!failedPackages.isEmpty()) {
- onHealthCheckFailed(observer, failedPackages);
- }
- if (observer.getMonitoredPackages().isEmpty() && (observer.registeredObserver == null
- || !observer.registeredObserver.isPersistent())) {
- Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired");
- it.remove();
- }
- }
- }
-
- private void onHealthCheckFailed(ObserverInternal observer,
- Set<MonitoredPackage> failedPackages) {
- mLongTaskHandler.post(() -> {
- synchronized (mLock) {
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null) {
- Iterator<MonitoredPackage> it = failedPackages.iterator();
- while (it.hasNext()) {
- VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
- if (versionedPkg != null) {
- Slog.i(TAG,
- "Explicit health check failed for package " + versionedPkg);
- registeredObserver.execute(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
- }
- }
- }
- }
- });
- }
-
- /**
- * Gets PackageInfo for the given package. Matches any user and apex.
- *
- * @throws PackageManager.NameNotFoundException if no such package is installed.
- */
- private PackageInfo getPackageInfo(String packageName)
- throws PackageManager.NameNotFoundException {
- PackageManager pm = mContext.getPackageManager();
- try {
- // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
- // flag, so make two separate attempts to get the package info.
- // We don't need both flags at the same time because we assume
- // apex files are always installed for all users.
- return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
- } catch (PackageManager.NameNotFoundException e) {
- return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
- }
- }
-
- @Nullable
- private VersionedPackage getVersionedPackage(String packageName) {
- final PackageManager pm = mContext.getPackageManager();
- if (pm == null || TextUtils.isEmpty(packageName)) {
- return null;
- }
- try {
- final long versionCode = getPackageInfo(packageName).getLongVersionCode();
- return new VersionedPackage(packageName, versionCode);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
- * Loads mAllObservers from file.
- *
- * <p>Note that this is <b>not</b> thread safe and should only called be called
- * from the constructor.
- */
- private void loadFromFile() {
- InputStream infile = null;
- mAllObservers.clear();
- try {
- infile = mPolicyFile.openRead();
- final TypedXmlPullParser parser = Xml.resolvePullParser(infile);
- XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
- int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- ObserverInternal observer = ObserverInternal.read(parser, this);
- if (observer != null) {
- mAllObservers.put(observer.name, observer);
- }
- }
- } catch (FileNotFoundException e) {
- // Nothing to monitor
- } catch (IOException | NumberFormatException | XmlPullParserException e) {
- Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
- mPolicyFile.delete();
- } finally {
- IoUtils.closeQuietly(infile);
- }
- }
-
- private void onPropertyChanged(DeviceConfig.Properties properties) {
- try {
- updateConfigs();
- } catch (Exception ignore) {
- Slog.w(TAG, "Failed to reload device config changes");
- }
- }
-
- /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */
- private void setPropertyChangedListenerLocked() {
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_ROLLBACK,
- mContext.getMainExecutor(),
- mOnPropertyChangedListener);
- }
-
- @VisibleForTesting
- void removePropertyChangedListener() {
- DeviceConfig.removeOnPropertiesChangedListener(mOnPropertyChangedListener);
- }
-
- /**
- * Health check is enabled or disabled after reading the flags
- * from DeviceConfig.
- */
- @VisibleForTesting
- void updateConfigs() {
- synchronized (mLock) {
- mTriggerFailureCount = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
- DEFAULT_TRIGGER_FAILURE_COUNT);
- if (mTriggerFailureCount <= 0) {
- mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
- }
-
- mTriggerFailureDurationMs = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
- DEFAULT_TRIGGER_FAILURE_DURATION_MS);
- if (mTriggerFailureDurationMs <= 0) {
- mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
- }
-
- setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
- DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED));
- }
- }
-
- private void registerConnectivityModuleHealthListener() {
- // TODO: have an internal method to trigger a rollback by reporting high severity errors,
- // and rely on ActivityManager to inform the watchdog of severe network stack crashes
- // instead of having this listener in parallel.
- mConnectivityModuleConnector.registerHealthListener(
- packageName -> {
- final VersionedPackage pkg = getVersionedPackage(packageName);
- if (pkg == null) {
- Slog.wtf(TAG, "NetworkStack failed but could not find its package");
- return;
- }
- final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
- onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
- });
- }
-
- /**
- * Persists mAllObservers to file. Threshold information is ignored.
- */
- private boolean saveToFile() {
- Slog.i(TAG, "Saving observer state to file");
- synchronized (mLock) {
- FileOutputStream stream;
- try {
- stream = mPolicyFile.startWrite();
- } catch (IOException e) {
- Slog.w(TAG, "Cannot update monitored packages", e);
- return false;
- }
-
- try {
- TypedXmlSerializer out = Xml.resolveSerializer(stream);
- out.startDocument(null, true);
- out.startTag(null, TAG_PACKAGE_WATCHDOG);
- out.attributeInt(null, ATTR_VERSION, DB_VERSION);
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- mAllObservers.valueAt(oIndex).writeLocked(out);
- }
- out.endTag(null, TAG_PACKAGE_WATCHDOG);
- out.endDocument();
- mPolicyFile.finishWrite(stream);
- return true;
- } catch (IOException e) {
- Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
- mPolicyFile.failWrite(stream);
- return false;
- }
- }
- }
-
- private void saveToFileAsync() {
- if (!mLongTaskHandler.hasCallbacks(mSaveToFile)) {
- mLongTaskHandler.post(mSaveToFile);
- }
- }
-
- /** Convert a {@code LongArrayQueue} to a String of comma-separated values. */
- public static String longArrayQueueToString(LongArrayQueue queue) {
- if (queue.size() > 0) {
- StringBuilder sb = new StringBuilder();
- sb.append(queue.get(0));
- for (int i = 1; i < queue.size(); i++) {
- sb.append(",");
- sb.append(queue.get(i));
- }
- return sb.toString();
- }
- return "";
- }
-
- /** Parse a comma-separated String of longs into a LongArrayQueue. */
- public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
- LongArrayQueue result = new LongArrayQueue();
- if (!TextUtils.isEmpty(commaSeparatedValues)) {
- String[] values = commaSeparatedValues.split(",");
- for (String value : values) {
- result.addLast(Long.parseLong(value));
- }
- }
- return result;
- }
-
-
- /** Dump status of every observer in mAllObservers. */
- public void dump(PrintWriter pw) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- ipw.println("Package Watchdog status");
- ipw.increaseIndent();
- synchronized (mLock) {
- for (String observerName : mAllObservers.keySet()) {
- ipw.println("Observer name: " + observerName);
- ipw.increaseIndent();
- ObserverInternal observerInternal = mAllObservers.get(observerName);
- observerInternal.dump(ipw);
- ipw.decreaseIndent();
- }
- }
- ipw.decreaseIndent();
- dumpCrashRecoveryEvents(ipw);
- }
-
- @VisibleForTesting
- @GuardedBy("mLock")
- void registerObserverInternal(ObserverInternal observerInternal) {
- mAllObservers.put(observerInternal.name, observerInternal);
- }
-
- /**
- * Represents an observer monitoring a set of packages along with the failure thresholds for
- * each package.
- *
- * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
- * instances of this class.
- */
- static class ObserverInternal {
- public final String name;
- @GuardedBy("mLock")
- private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
- @Nullable
- @GuardedBy("mLock")
- public PackageHealthObserver registeredObserver;
- private int mMitigationCount;
-
- ObserverInternal(String name, List<MonitoredPackage> packages) {
- this(name, packages, /*mitigationCount=*/ 0);
- }
-
- ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) {
- this.name = name;
- updatePackagesLocked(packages);
- this.mMitigationCount = mitigationCount;
- }
-
- /**
- * Writes important {@link MonitoredPackage} details for this observer to file.
- * Does not persist any package failure thresholds.
- */
- @GuardedBy("mLock")
- public boolean writeLocked(TypedXmlSerializer out) {
- try {
- out.startTag(null, TAG_OBSERVER);
- out.attribute(null, ATTR_NAME, name);
- if (Flags.recoverabilityDetection()) {
- out.attributeInt(null, ATTR_MITIGATION_COUNT, mMitigationCount);
- }
- for (int i = 0; i < mPackages.size(); i++) {
- MonitoredPackage p = mPackages.valueAt(i);
- p.writeLocked(out);
- }
- out.endTag(null, TAG_OBSERVER);
- return true;
- } catch (IOException e) {
- Slog.w(TAG, "Cannot save observer", e);
- return false;
- }
- }
-
- public int getBootMitigationCount() {
- return mMitigationCount;
- }
-
- public void setBootMitigationCount(int mitigationCount) {
- mMitigationCount = mitigationCount;
- }
-
- @GuardedBy("mLock")
- public void updatePackagesLocked(List<MonitoredPackage> packages) {
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- MonitoredPackage p = packages.get(pIndex);
- MonitoredPackage existingPackage = getMonitoredPackage(p.getName());
- if (existingPackage != null) {
- existingPackage.updateHealthCheckDuration(p.mDurationMs);
- } else {
- putMonitoredPackage(p);
- }
- }
- }
-
- /**
- * Reduces the monitoring durations of all packages observed by this observer by
- * {@code elapsedMs}. If any duration is less than 0, the package is removed from
- * observation. If any health check duration is less than 0, the health check result
- * is evaluated.
- *
- * @return a {@link Set} of packages that were removed from the observer without explicit
- * health check passing, or an empty list if no package expired for which an explicit health
- * check was still pending
- */
- @GuardedBy("mLock")
- private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
- Set<MonitoredPackage> failedPackages = new ArraySet<>();
- Iterator<MonitoredPackage> it = mPackages.values().iterator();
- while (it.hasNext()) {
- MonitoredPackage p = it.next();
- int oldState = p.getHealthCheckStateLocked();
- int newState = p.handleElapsedTimeLocked(elapsedMs);
- if (oldState != HealthCheckState.FAILED
- && newState == HealthCheckState.FAILED) {
- Slog.i(TAG, "Package " + p.getName() + " failed health check");
- failedPackages.add(p);
- }
- if (p.isExpiredLocked()) {
- it.remove();
- }
- }
- return failedPackages;
- }
-
- /**
- * Increments failure counts of {@code packageName}.
- * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
- */
- @GuardedBy("mLock")
- public boolean onPackageFailureLocked(String packageName) {
- if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
- && registeredObserver.mayObservePackage(packageName)) {
- putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
- packageName, DEFAULT_OBSERVING_DURATION_MS, false));
- }
- MonitoredPackage p = getMonitoredPackage(packageName);
- if (p != null) {
- return p.onFailureLocked();
- }
- return false;
- }
-
- /**
- * Returns the map of packages monitored by this observer.
- *
- * @return a mapping of package names to {@link MonitoredPackage} objects.
- */
- @GuardedBy("mLock")
- public ArrayMap<String, MonitoredPackage> getMonitoredPackages() {
- return mPackages;
- }
-
- /**
- * Returns the {@link MonitoredPackage} associated with a given package name if the
- * package is being monitored by this observer.
- *
- * @param packageName: the name of the package.
- * @return the {@link MonitoredPackage} object associated with the package name if one
- * exists, {@code null} otherwise.
- */
- @GuardedBy("mLock")
- @Nullable
- public MonitoredPackage getMonitoredPackage(String packageName) {
- return mPackages.get(packageName);
- }
-
- /**
- * Associates a {@link MonitoredPackage} with the observer.
- *
- * @param p: the {@link MonitoredPackage} to store.
- */
- @GuardedBy("mLock")
- public void putMonitoredPackage(MonitoredPackage p) {
- mPackages.put(p.getName(), p);
- }
-
- /**
- * Returns one ObserverInternal from the {@code parser} and advances its state.
- *
- * <p>Note that this method is <b>not</b> thread safe. It should only be called from
- * #loadFromFile which in turn is only called on construction of the
- * singleton PackageWatchdog.
- **/
- public static ObserverInternal read(TypedXmlPullParser parser, PackageWatchdog watchdog) {
- String observerName = null;
- int observerMitigationCount = 0;
- if (TAG_OBSERVER.equals(parser.getName())) {
- observerName = parser.getAttributeValue(null, ATTR_NAME);
- if (TextUtils.isEmpty(observerName)) {
- Slog.wtf(TAG, "Unable to read observer name");
- return null;
- }
- }
- List<MonitoredPackage> packages = new ArrayList<>();
- int innerDepth = parser.getDepth();
- try {
- if (Flags.recoverabilityDetection()) {
- try {
- observerMitigationCount =
- parser.getAttributeInt(null, ATTR_MITIGATION_COUNT);
- } catch (XmlPullParserException e) {
- Slog.i(
- TAG,
- "ObserverInternal mitigation count was not present.");
- }
- }
- while (XmlUtils.nextElementWithin(parser, innerDepth)) {
- if (TAG_PACKAGE.equals(parser.getName())) {
- try {
- MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser);
- if (pkg != null) {
- packages.add(pkg);
- }
- } catch (NumberFormatException e) {
- Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
- continue;
- }
- }
- }
- } catch (XmlPullParserException | IOException e) {
- Slog.wtf(TAG, "Unable to read observer " + observerName, e);
- return null;
- }
- if (packages.isEmpty()) {
- return null;
- }
- return new ObserverInternal(observerName, packages, observerMitigationCount);
- }
-
- /** Dumps information about this observer and the packages it watches. */
- public void dump(IndentingPrintWriter pw) {
- boolean isPersistent = registeredObserver != null && registeredObserver.isPersistent();
- pw.println("Persistent: " + isPersistent);
- for (String packageName : mPackages.keySet()) {
- MonitoredPackage p = getMonitoredPackage(packageName);
- pw.println(packageName + ": ");
- pw.increaseIndent();
- pw.println("# Failures: " + p.mFailureHistory.size());
- pw.println("Monitoring duration remaining: " + p.mDurationMs + "ms");
- pw.println("Explicit health check duration: " + p.mHealthCheckDurationMs + "ms");
- pw.println("Health check state: " + p.toString(p.mHealthCheckState));
- pw.decreaseIndent();
- }
- }
- }
-
- @Retention(SOURCE)
- @IntDef(value = {
- HealthCheckState.ACTIVE,
- HealthCheckState.INACTIVE,
- HealthCheckState.PASSED,
- HealthCheckState.FAILED})
- public @interface HealthCheckState {
- // The package has not passed health check but has requested a health check
- int ACTIVE = 0;
- // The package has not passed health check and has not requested a health check
- int INACTIVE = 1;
- // The package has passed health check
- int PASSED = 2;
- // The package has failed health check
- int FAILED = 3;
- }
-
- MonitoredPackage newMonitoredPackage(
- String name, long durationMs, boolean hasPassedHealthCheck) {
- return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck,
- new LongArrayQueue());
- }
-
- MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
- boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
- return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
- hasPassedHealthCheck, mitigationCalls);
- }
-
- MonitoredPackage parseMonitoredPackage(TypedXmlPullParser parser)
- throws XmlPullParserException {
- String packageName = parser.getAttributeValue(null, ATTR_NAME);
- long duration = parser.getAttributeLong(null, ATTR_DURATION);
- long healthCheckDuration = parser.getAttributeLong(null,
- ATTR_EXPLICIT_HEALTH_CHECK_DURATION);
- boolean hasPassedHealthCheck = parser.getAttributeBoolean(null, ATTR_PASSED_HEALTH_CHECK);
- LongArrayQueue mitigationCalls = parseLongArrayQueue(
- parser.getAttributeValue(null, ATTR_MITIGATION_CALLS));
- return newMonitoredPackage(packageName,
- duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls);
- }
-
- /**
- * Represents a package and its health check state along with the time
- * it should be monitored for.
- *
- * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
- * instances of this class.
- */
- class MonitoredPackage {
- private final String mPackageName;
- // Times when package failures happen sorted in ascending order
- @GuardedBy("mLock")
- private final LongArrayQueue mFailureHistory = new LongArrayQueue();
- // Times when an observer was called to mitigate this package's failure. Sorted in
- // ascending order.
- @GuardedBy("mLock")
- private final LongArrayQueue mMitigationCalls;
- // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
- // methods that could change the health check state: handleElapsedTimeLocked and
- // tryPassHealthCheckLocked
- private int mHealthCheckState = HealthCheckState.INACTIVE;
- // Whether an explicit health check has passed.
- // This value in addition with mHealthCheckDurationMs determines the health check state
- // of the package, see #getHealthCheckStateLocked
- @GuardedBy("mLock")
- private boolean mHasPassedHealthCheck;
- // System uptime duration to monitor package.
- @GuardedBy("mLock")
- private long mDurationMs;
- // System uptime duration to check the result of an explicit health check
- // Initially, MAX_VALUE until we get a value from the health check service
- // and request health checks.
- // This value in addition with mHasPassedHealthCheck determines the health check state
- // of the package, see #getHealthCheckStateLocked
- @GuardedBy("mLock")
- private long mHealthCheckDurationMs = Long.MAX_VALUE;
-
- MonitoredPackage(String packageName, long durationMs,
- long healthCheckDurationMs, boolean hasPassedHealthCheck,
- LongArrayQueue mitigationCalls) {
- mPackageName = packageName;
- mDurationMs = durationMs;
- mHealthCheckDurationMs = healthCheckDurationMs;
- mHasPassedHealthCheck = hasPassedHealthCheck;
- mMitigationCalls = mitigationCalls;
- updateHealthCheckStateLocked();
- }
-
- /** Writes the salient fields to disk using {@code out}. */
- @GuardedBy("mLock")
- public void writeLocked(TypedXmlSerializer out) throws IOException {
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATTR_NAME, getName());
- out.attributeLong(null, ATTR_DURATION, mDurationMs);
- out.attributeLong(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION, mHealthCheckDurationMs);
- out.attributeBoolean(null, ATTR_PASSED_HEALTH_CHECK, mHasPassedHealthCheck);
- LongArrayQueue normalizedCalls = normalizeMitigationCalls();
- out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls));
- out.endTag(null, TAG_PACKAGE);
- }
-
- /**
- * Increment package failures or resets failure count depending on the last package failure.
- *
- * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
- */
- @GuardedBy("mLock")
- public boolean onFailureLocked() {
- // Sliding window algorithm: find out if there exists a window containing failures >=
- // mTriggerFailureCount.
- final long now = mSystemClock.uptimeMillis();
- mFailureHistory.addLast(now);
- while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
- // Prune values falling out of the window
- mFailureHistory.removeFirst();
- }
- boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
- if (failed) {
- mFailureHistory.clear();
- }
- return failed;
- }
-
- /**
- * Notes the timestamp of a mitigation call into the observer.
- */
- @GuardedBy("mLock")
- public void noteMitigationCallLocked() {
- mMitigationCalls.addLast(mSystemClock.uptimeMillis());
- }
-
- /**
- * Prunes any mitigation calls outside of the de-escalation window, and returns the
- * number of calls that are in the window afterwards.
- *
- * @return the number of mitigation calls made in the de-escalation window.
- */
- @GuardedBy("mLock")
- public int getMitigationCountLocked() {
- try {
- final long now = mSystemClock.uptimeMillis();
- while (now - mMitigationCalls.peekFirst() > DEFAULT_DEESCALATION_WINDOW_MS) {
- mMitigationCalls.removeFirst();
- }
- } catch (NoSuchElementException ignore) {
- }
-
- return mMitigationCalls.size();
- }
-
- /**
- * Before writing to disk, make the mitigation call timestamps relative to the current
- * system uptime. This is because they need to be relative to the uptime which will reset
- * at the next boot.
- *
- * @return a LongArrayQueue of the mitigation calls relative to the current system uptime.
- */
- @GuardedBy("mLock")
- public LongArrayQueue normalizeMitigationCalls() {
- LongArrayQueue normalized = new LongArrayQueue();
- final long now = mSystemClock.uptimeMillis();
- for (int i = 0; i < mMitigationCalls.size(); i++) {
- normalized.addLast(mMitigationCalls.get(i) - now);
- }
- return normalized;
- }
-
- /**
- * Sets the initial health check duration.
- *
- * @return the new health check state
- */
- @GuardedBy("mLock")
- public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
- if (initialHealthCheckDurationMs <= 0) {
- Slog.wtf(TAG, "Cannot set non-positive health check duration "
- + initialHealthCheckDurationMs + "ms for package " + getName()
- + ". Using total duration " + mDurationMs + "ms instead");
- initialHealthCheckDurationMs = mDurationMs;
- }
- if (mHealthCheckState == HealthCheckState.INACTIVE) {
- // Transitions to ACTIVE
- mHealthCheckDurationMs = initialHealthCheckDurationMs;
- }
- return updateHealthCheckStateLocked();
- }
-
- /**
- * Updates the monitoring durations of the package.
- *
- * @return the new health check state
- */
- @GuardedBy("mLock")
- public int handleElapsedTimeLocked(long elapsedMs) {
- if (elapsedMs <= 0) {
- Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName());
- return mHealthCheckState;
- }
- // Transitions to FAILED if now <= 0 and health check not passed
- mDurationMs -= elapsedMs;
- if (mHealthCheckState == HealthCheckState.ACTIVE) {
- // We only update health check durations if we have #setHealthCheckActiveLocked
- // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
- // Transitions to FAILED if now <= 0 and health check not passed
- mHealthCheckDurationMs -= elapsedMs;
- }
- return updateHealthCheckStateLocked();
- }
-
- /** Explicitly update the monitoring duration of the package. */
- @GuardedBy("mLock")
- public void updateHealthCheckDuration(long newDurationMs) {
- mDurationMs = newDurationMs;
- }
-
- /**
- * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
- * if not yet {@link HealthCheckState.FAILED}.
- *
- * @return the new {@link HealthCheckState health check state}
- */
- @GuardedBy("mLock")
- @HealthCheckState
- public int tryPassHealthCheckLocked() {
- if (mHealthCheckState != HealthCheckState.FAILED) {
- // FAILED is a final state so only pass if we haven't failed
- // Transition to PASSED
- mHasPassedHealthCheck = true;
- }
- return updateHealthCheckStateLocked();
- }
-
- /** Returns the monitored package name. */
- private String getName() {
- return mPackageName;
- }
-
- /**
- * Returns the current {@link HealthCheckState health check state}.
- */
- @GuardedBy("mLock")
- @HealthCheckState
- public int getHealthCheckStateLocked() {
- return mHealthCheckState;
- }
-
- /**
- * Returns the shortest duration before the package should be scheduled for a prune.
- *
- * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled
- */
- @GuardedBy("mLock")
- public long getShortestScheduleDurationMsLocked() {
- // Consider health check duration only if #isPendingHealthChecksLocked is true
- return Math.min(toPositive(mDurationMs),
- isPendingHealthChecksLocked()
- ? toPositive(mHealthCheckDurationMs) : Long.MAX_VALUE);
- }
-
- /**
- * Returns {@code true} if the total duration left to monitor the package is less than or
- * equal to 0 {@code false} otherwise.
- */
- @GuardedBy("mLock")
- public boolean isExpiredLocked() {
- return mDurationMs <= 0;
- }
-
- /**
- * Returns {@code true} if the package, {@link #getName} is expecting health check results
- * {@code false} otherwise.
- */
- @GuardedBy("mLock")
- public boolean isPendingHealthChecksLocked() {
- return mHealthCheckState == HealthCheckState.ACTIVE
- || mHealthCheckState == HealthCheckState.INACTIVE;
- }
-
- /**
- * Updates the health check state based on {@link #mHasPassedHealthCheck}
- * and {@link #mHealthCheckDurationMs}.
- *
- * @return the new {@link HealthCheckState health check state}
- */
- @GuardedBy("mLock")
- @HealthCheckState
- private int updateHealthCheckStateLocked() {
- int oldState = mHealthCheckState;
- if (mHasPassedHealthCheck) {
- // Set final state first to avoid ambiguity
- mHealthCheckState = HealthCheckState.PASSED;
- } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
- // Set final state first to avoid ambiguity
- mHealthCheckState = HealthCheckState.FAILED;
- } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
- mHealthCheckState = HealthCheckState.INACTIVE;
- } else {
- mHealthCheckState = HealthCheckState.ACTIVE;
- }
-
- if (oldState != mHealthCheckState) {
- Slog.i(TAG, "Updated health check state for package " + getName() + ": "
- + toString(oldState) + " -> " + toString(mHealthCheckState));
- }
- return mHealthCheckState;
- }
-
- /** Returns a {@link String} representation of the current health check state. */
- private String toString(@HealthCheckState int state) {
- switch (state) {
- case HealthCheckState.ACTIVE:
- return "ACTIVE";
- case HealthCheckState.INACTIVE:
- return "INACTIVE";
- case HealthCheckState.PASSED:
- return "PASSED";
- case HealthCheckState.FAILED:
- return "FAILED";
- default:
- return "UNKNOWN";
- }
- }
-
- /** Returns {@code value} if it is greater than 0 or {@link Long#MAX_VALUE} otherwise. */
- private long toPositive(long value) {
- return value > 0 ? value : Long.MAX_VALUE;
- }
-
- /** Compares the equality of this object with another {@link MonitoredPackage}. */
- @VisibleForTesting
- boolean isEqualTo(MonitoredPackage pkg) {
- return (getName().equals(pkg.getName()))
- && mDurationMs == pkg.mDurationMs
- && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck
- && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs
- && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString());
- }
- }
-
- @GuardedBy("mLock")
- @SuppressWarnings("GuardedBy")
- void saveAllObserversBootMitigationCountToMetadata(String filePath) {
- HashMap<String, Integer> bootMitigationCounts = new HashMap<>();
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
- }
-
- FileOutputStream fileStream = null;
- ObjectOutputStream objectStream = null;
- try {
- fileStream = new FileOutputStream(new File(filePath));
- objectStream = new ObjectOutputStream(fileStream);
- objectStream.writeObject(bootMitigationCounts);
- objectStream.flush();
- } catch (Exception e) {
- Slog.i(TAG, "Could not save observers metadata to file: " + e);
- return;
- } finally {
- IoUtils.closeQuietly(objectStream);
- IoUtils.closeQuietly(fileStream);
- }
- }
-
- /**
- * Handles the thresholding logic for system server boots.
- */
- class BootThreshold {
-
- private final int mBootTriggerCount;
- private final long mTriggerWindow;
-
- BootThreshold(int bootTriggerCount, long triggerWindow) {
- this.mBootTriggerCount = bootTriggerCount;
- this.mTriggerWindow = triggerWindow;
- }
-
- public void reset() {
- setStart(0);
- setCount(0);
- }
-
- protected int getCount() {
- return CrashRecoveryProperties.rescueBootCount().orElse(0);
- }
-
- protected void setCount(int count) {
- CrashRecoveryProperties.rescueBootCount(count);
- }
-
- public long getStart() {
- return CrashRecoveryProperties.rescueBootStart().orElse(0L);
- }
-
- public int getMitigationCount() {
- return CrashRecoveryProperties.bootMitigationCount().orElse(0);
- }
-
- public void setStart(long start) {
- CrashRecoveryProperties.rescueBootStart(getStartTime(start));
- }
-
- public void setMitigationStart(long start) {
- CrashRecoveryProperties.bootMitigationStart(getStartTime(start));
- }
-
- public long getMitigationStart() {
- return CrashRecoveryProperties.bootMitigationStart().orElse(0L);
- }
-
- public void setMitigationCount(int count) {
- CrashRecoveryProperties.bootMitigationCount(count);
- }
-
- private static long constrain(long amount, long low, long high) {
- return amount < low ? low : (amount > high ? high : amount);
- }
-
- public long getStartTime(long start) {
- final long now = mSystemClock.uptimeMillis();
- return constrain(start, 0, now);
- }
-
- public void saveMitigationCountToMetadata() {
- try (BufferedWriter writer = new BufferedWriter(new FileWriter(METADATA_FILE))) {
- writer.write(String.valueOf(getMitigationCount()));
- } catch (Exception e) {
- Slog.e(TAG, "Could not save metadata to file: " + e);
- }
- }
-
- public void readMitigationCountFromMetadataIfNecessary() {
- File bootPropsFile = new File(METADATA_FILE);
- if (bootPropsFile.exists()) {
- try (BufferedReader reader = new BufferedReader(new FileReader(METADATA_FILE))) {
- String mitigationCount = reader.readLine();
- setMitigationCount(Integer.parseInt(mitigationCount));
- bootPropsFile.delete();
- } catch (Exception e) {
- Slog.i(TAG, "Could not read metadata file: " + e);
- }
- }
- }
-
-
- /** Increments the boot counter, and returns whether the device is bootlooping. */
- @GuardedBy("mLock")
- public boolean incrementAndTest() {
- if (Flags.recoverabilityDetection()) {
- readAllObserversBootMitigationCountIfNecessary(METADATA_FILE);
- } else {
- readMitigationCountFromMetadataIfNecessary();
- }
-
- final long now = mSystemClock.uptimeMillis();
- if (now - getStart() < 0) {
- Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
- setStart(now);
- setMitigationStart(now);
- }
- if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) {
- setMitigationStart(now);
- if (Flags.recoverabilityDetection()) {
- resetAllObserversBootMitigationCount();
- } else {
- setMitigationCount(0);
- }
- }
- final long window = now - getStart();
- if (window >= mTriggerWindow) {
- setCount(1);
- setStart(now);
- return false;
- } else {
- int count = getCount() + 1;
- setCount(count);
- EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
- if (Flags.recoverabilityDetection()) {
- // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
- // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
- return (count >= mBootTriggerCount)
- || (performedMitigationsDuringWindow() && count > 1);
- }
- return count >= mBootTriggerCount;
- }
- }
-
- @GuardedBy("mLock")
- private boolean performedMitigationsDuringWindow() {
- for (ObserverInternal observerInternal: mAllObservers.values()) {
- if (observerInternal.getBootMitigationCount() > 0) {
- return true;
- }
- }
- return false;
- }
-
- @GuardedBy("mLock")
- private void resetAllObserversBootMitigationCount() {
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- observer.setBootMitigationCount(0);
- }
- saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- }
-
- @GuardedBy("mLock")
- @SuppressWarnings("GuardedBy")
- void readAllObserversBootMitigationCountIfNecessary(String filePath) {
- File metadataFile = new File(filePath);
- if (metadataFile.exists()) {
- FileInputStream fileStream = null;
- ObjectInputStream objectStream = null;
- HashMap<String, Integer> bootMitigationCounts = null;
- try {
- fileStream = new FileInputStream(metadataFile);
- objectStream = new ObjectInputStream(fileStream);
- bootMitigationCounts =
- (HashMap<String, Integer>) objectStream.readObject();
- } catch (Exception e) {
- Slog.i(TAG, "Could not read observer metadata file: " + e);
- return;
- } finally {
- IoUtils.closeQuietly(objectStream);
- IoUtils.closeQuietly(fileStream);
- }
-
- if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) {
- Slog.i(TAG, "No observer in metadata file");
- return;
- }
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- if (bootMitigationCounts.containsKey(observer.name)) {
- observer.setBootMitigationCount(
- bootMitigationCounts.get(observer.name));
- }
- }
- }
- }
- }
-
- /**
- * Register broadcast receiver for shutdown.
- * We would save the observer state to persist across boots.
- *
- * @hide
- */
- public void registerShutdownBroadcastReceiver() {
- BroadcastReceiver shutdownEventReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Only write if intent is relevant to device reboot or shutdown.
- String intentAction = intent.getAction();
- if (ACTION_REBOOT.equals(intentAction)
- || ACTION_SHUTDOWN.equals(intentAction)) {
- writeNow();
- }
- }
- };
-
- // Setup receiver for device reboots or shutdowns.
- IntentFilter filter = new IntentFilter(ACTION_REBOOT);
- filter.addAction(ACTION_SHUTDOWN);
- mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null,
- /* run on main thread */ null);
- }
-}
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
deleted file mode 100644
index a6e21fccd294..000000000000
--- a/services/core/java/com/android/server/RescueParty.java
+++ /dev/null
@@ -1,1090 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.provider.DeviceConfig.Properties;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
-import android.os.Build;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.PowerManager;
-import android.os.RecoverySystem;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.sysprop.CrashRecoveryProperties;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.server.PackageWatchdog.FailureReasons;
-import com.android.server.PackageWatchdog.PackageHealthObserver;
-import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.am.SettingsToPropertiesMapper;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utilities to help rescue the system from crash loops. Callers are expected to
- * report boot events and persistent app crashes, and if they happen frequently
- * enough this class will slowly escalate through several rescue operations
- * before finally rebooting and prompting the user if they want to wipe data as
- * a last resort.
- *
- * @hide
- */
-public class RescueParty {
- @VisibleForTesting
- static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
- @VisibleForTesting
- static final int LEVEL_NONE = 0;
- @VisibleForTesting
- static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
- @VisibleForTesting
- static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
- @VisibleForTesting
- static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
- @VisibleForTesting
- static final int LEVEL_WARM_REBOOT = 4;
- @VisibleForTesting
- static final int LEVEL_FACTORY_RESET = 5;
- @VisibleForTesting
- static final int RESCUE_LEVEL_NONE = 0;
- @VisibleForTesting
- static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1;
- @VisibleForTesting
- static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2;
- @VisibleForTesting
- static final int RESCUE_LEVEL_WARM_REBOOT = 3;
- @VisibleForTesting
- static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4;
- @VisibleForTesting
- static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5;
- @VisibleForTesting
- static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6;
- @VisibleForTesting
- static final int RESCUE_LEVEL_FACTORY_RESET = 7;
-
- @IntDef(prefix = { "RESCUE_LEVEL_" }, value = {
- RESCUE_LEVEL_NONE,
- RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET,
- RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET,
- RESCUE_LEVEL_WARM_REBOOT,
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
- RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
- RESCUE_LEVEL_FACTORY_RESET
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface RescueLevels {}
-
- @VisibleForTesting
- static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit";
- @VisibleForTesting
- static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1;
- @VisibleForTesting
- static final String TAG = "RescueParty";
- @VisibleForTesting
- static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
- @VisibleForTesting
- static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS;
- // The DeviceConfig namespace containing all RescueParty switches.
- @VisibleForTesting
- static final String NAMESPACE_CONFIGURATION = "configuration";
- @VisibleForTesting
- static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
- "namespace_to_package_mapping";
- @VisibleForTesting
- static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
-
- private static final String NAME = "rescue-party-observer";
-
- private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
- private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
- private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
- "persist.device_config.configuration.disable_rescue_party";
- private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
- "persist.device_config.configuration.disable_rescue_party_factory_reset";
- private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
- "persist.device_config.configuration.rescue_party_throttle_duration_min";
-
- private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
- | ApplicationInfo.FLAG_SYSTEM;
-
- /** Register the Rescue Party observer as a Package Watchdog health observer */
- public static void registerHealthObserver(Context context) {
- PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
- }
-
- private static boolean isDisabled() {
- // Check if we're explicitly enabled for testing
- if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
- return false;
- }
-
- // We're disabled if the DeviceConfig disable flag is set to true.
- // This is in case that an emergency rollback of the feature is needed.
- if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
- Slog.v(TAG, "Disabled because of DeviceConfig flag");
- return true;
- }
-
- // We're disabled on all engineering devices
- if (Build.TYPE.equals("eng")) {
- Slog.v(TAG, "Disabled because of eng build");
- return true;
- }
-
- // We're disabled on userdebug devices connected over USB, since that's
- // a decent signal that someone is actively trying to debug the device,
- // or that it's in a lab environment.
- if (Build.TYPE.equals("userdebug") && isUsbActive()) {
- Slog.v(TAG, "Disabled because of active USB connection");
- return true;
- }
-
- // One last-ditch check
- if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
- Slog.v(TAG, "Disabled because of manual property");
- return true;
- }
-
- return false;
- }
-
- /**
- * Check if we're currently attempting to reboot for a factory reset. This method must
- * return true if RescueParty tries to reboot early during a boot loop, since the device
- * will not be fully booted at this time.
- */
- public static boolean isRecoveryTriggeredReboot() {
- return isFactoryResetPropertySet() || isRebootPropertySet();
- }
-
- static boolean isFactoryResetPropertySet() {
- return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
- }
-
- static boolean isRebootPropertySet() {
- return CrashRecoveryProperties.attemptingReboot().orElse(false);
- }
-
- protected static long getLastFactoryResetTimeMs() {
- return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L);
- }
-
- protected static int getMaxRescueLevelAttempted() {
- return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE);
- }
-
- protected static void setFactoryResetProperty(boolean value) {
- CrashRecoveryProperties.attemptingFactoryReset(value);
- }
- protected static void setRebootProperty(boolean value) {
- CrashRecoveryProperties.attemptingReboot(value);
- }
-
- protected static void setLastFactoryResetTimeMs(long value) {
- CrashRecoveryProperties.lastFactoryResetTimeMs(value);
- }
-
- protected static void setMaxRescueLevelAttempted(int level) {
- CrashRecoveryProperties.maxRescueLevelAttempted(level);
- }
-
- /**
- * Called when {@code SettingsProvider} has been published, which is a good
- * opportunity to reset any settings depending on our rescue level.
- */
- public static void onSettingsProviderPublished(Context context) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- handleNativeRescuePartyResets();
- ContentResolver contentResolver = context.getContentResolver();
- DeviceConfig.setMonitorCallback(
- contentResolver,
- Executors.newSingleThreadExecutor(),
- new RescuePartyMonitorCallback(context));
- }
- }
-
-
- /**
- * Called when {@code RollbackManager} performs Mainline module rollbacks,
- * to avoid rolled back modules consuming flag values only expected to work
- * on modules of newer versions.
- */
- public static void resetDeviceConfigForPackages(List<String> packageNames) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- if (packageNames == null) {
- return;
- }
- Set<String> namespacesToReset = new ArraySet<String>();
- Iterator<String> it = packageNames.iterator();
- RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated();
- // Get runtime package to namespace mapping if created.
- if (rescuePartyObserver != null) {
- while (it.hasNext()) {
- String packageName = it.next();
- Set<String> runtimeAffectedNamespaces =
- rescuePartyObserver.getAffectedNamespaceSet(packageName);
- if (runtimeAffectedNamespaces != null) {
- namespacesToReset.addAll(runtimeAffectedNamespaces);
- }
- }
- }
- // Get preset package to namespace mapping if created.
- Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages(
- packageNames);
- if (presetAffectedNamespaces != null) {
- namespacesToReset.addAll(presetAffectedNamespaces);
- }
-
- // Clear flags under the namespaces mapped to these packages.
- // Using setProperties since DeviceConfig.resetToDefaults bans the current flag set.
- Iterator<String> namespaceIt = namespacesToReset.iterator();
- while (namespaceIt.hasNext()) {
- String namespaceToReset = namespaceIt.next();
- Properties properties = new Properties.Builder(namespaceToReset).build();
- try {
- if (!DeviceConfig.setProperties(properties)) {
- logCrashRecoveryEvent(Log.ERROR, "Failed to clear properties under "
- + namespaceToReset
- + ". Running `device_config get_sync_disabled_for_tests` will confirm"
- + " if config-bulk-update is enabled.");
- }
- } catch (DeviceConfig.BadConfigException exception) {
- logCrashRecoveryEvent(Log.WARN, "namespace " + namespaceToReset
- + " is already banned, skip reset.");
- }
- }
- }
- }
-
- private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) {
- Set<String> resultSet = new ArraySet<String>();
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- try {
- String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION,
- NAMESPACE_TO_PACKAGE_MAPPING_FLAG, "");
- String[] mappingEntries = flagVal.split(",");
- for (int i = 0; i < mappingEntries.length; i++) {
- if (TextUtils.isEmpty(mappingEntries[i])) {
- continue;
- }
- String[] splitEntry = mappingEntries[i].split(":");
- if (splitEntry.length != 2) {
- throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]);
- }
- String namespace = splitEntry[0];
- String packageName = splitEntry[1];
-
- if (packageNames.contains(packageName)) {
- resultSet.add(namespace);
- }
- }
- } catch (Exception e) {
- resultSet.clear();
- Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e);
- } finally {
- return resultSet;
- }
- } else {
- return resultSet;
- }
- }
-
- @VisibleForTesting
- static long getElapsedRealtime() {
- return SystemClock.elapsedRealtime();
- }
-
- private static class RescuePartyMonitorCallback implements DeviceConfig.MonitorCallback {
- Context mContext;
-
- RescuePartyMonitorCallback(Context context) {
- this.mContext = context;
- }
-
- public void onNamespaceUpdate(@NonNull String updatedNamespace) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- startObservingPackages(mContext, updatedNamespace);
- }
- }
-
- public void onDeviceConfigAccess(@NonNull String callingPackage,
- @NonNull String namespace) {
-
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- RescuePartyObserver.getInstance(mContext).recordDeviceConfigAccess(
- callingPackage,
- namespace);
- }
- }
- }
-
- private static void startObservingPackages(Context context, @NonNull String updatedNamespace) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
- Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet(
- updatedNamespace);
- if (callingPackages == null) {
- return;
- }
- List<String> callingPackageList = new ArrayList<>();
- callingPackageList.addAll(callingPackages);
- Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
- + updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
- rescuePartyObserver,
- callingPackageList,
- DEFAULT_OBSERVING_DURATION_MS);
- }
- }
-
- private static void handleNativeRescuePartyResets() {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
- String[] resetNativeCategories =
- SettingsToPropertiesMapper.getResetNativeCategories();
- for (int i = 0; i < resetNativeCategories.length; i++) {
- // Don't let RescueParty reset the namespace for RescueParty switches.
- if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) {
- continue;
- }
- DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE,
- resetNativeCategories[i]);
- }
- }
- }
- }
-
- private static int getMaxRescueLevel(boolean mayPerformReboot) {
- if (Flags.recoverabilityDetection()) {
- if (!mayPerformReboot
- || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT,
- DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT);
- }
- return RESCUE_LEVEL_FACTORY_RESET;
- } else {
- if (!mayPerformReboot
- || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
- }
- return LEVEL_FACTORY_RESET;
- }
- }
-
- private static int getMaxRescueLevel() {
- if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return Level.factoryReset();
- }
- return Level.reboot();
- }
-
- /**
- * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
- *
- * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
- * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
- * for the given failure.
- * @return the rescue level for the n-th mitigation attempt.
- */
- private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- if (mitigationCount == 1) {
- return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
- } else if (mitigationCount == 2) {
- return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
- } else if (mitigationCount == 3) {
- return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
- } else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
- } else if (mitigationCount >= 5) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
- } else {
- Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
- return LEVEL_NONE;
- }
- } else {
- if (mitigationCount == 1) {
- return Level.reboot();
- } else if (mitigationCount >= 2) {
- return Math.min(getMaxRescueLevel(), Level.factoryReset());
- } else {
- Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
- return LEVEL_NONE;
- }
- }
- }
-
- /**
- * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
- * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and
- * all device config reset). Behaves as if one mitigation attempt was already done.
- *
- * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
- * @param mayPerformReboot whether or not a reboot and factory reset may be performed
- * for the given failure.
- * @param failedPackage in case of bootloop this is null.
- * @return the rescue level for the n-th mitigation attempt.
- */
- private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot,
- @Nullable VersionedPackage failedPackage) {
- // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed
- // package.
- if (failedPackage == null && mitigationCount > 0) {
- mitigationCount += 1;
- }
- if (mitigationCount == 1) {
- return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET;
- } else if (mitigationCount == 2) {
- return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET;
- } else if (mitigationCount == 3) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT);
- } else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(mayPerformReboot),
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
- } else if (mitigationCount == 5) {
- return Math.min(getMaxRescueLevel(mayPerformReboot),
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
- } else if (mitigationCount == 6) {
- return Math.min(getMaxRescueLevel(mayPerformReboot),
- RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
- } else if (mitigationCount >= 7) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET);
- } else {
- return RESCUE_LEVEL_NONE;
- }
- }
-
- /**
- * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
- *
- * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
- * @return the rescue level for the n-th mitigation attempt.
- */
- private static @RescueLevels int getRescueLevel(int mitigationCount) {
- if (mitigationCount == 1) {
- return Level.reboot();
- } else if (mitigationCount >= 2) {
- return Math.min(getMaxRescueLevel(), Level.factoryReset());
- } else {
- return Level.none();
- }
- }
-
- private static void executeRescueLevel(Context context, @Nullable String failedPackage,
- int level) {
- Slog.w(TAG, "Attempting rescue level " + levelToString(level));
- try {
- executeRescueLevelInternal(context, level, failedPackage);
- EventLogTags.writeRescueSuccess(level);
- String successMsg = "Finished rescue level " + levelToString(level);
- if (!TextUtils.isEmpty(failedPackage)) {
- successMsg += " for package " + failedPackage;
- }
- logCrashRecoveryEvent(Log.DEBUG, successMsg);
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- }
-
- private static void executeRescueLevelInternal(Context context, int level, @Nullable
- String failedPackage) throws Exception {
- if (Flags.recoverabilityDetection()) {
- executeRescueLevelInternalNew(context, level, failedPackage);
- } else {
- executeRescueLevelInternalOld(context, level, failedPackage);
- }
- }
-
- private static void executeRescueLevelInternalOld(Context context, int level, @Nullable
- String failedPackage) throws Exception {
- CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
- level, levelToString(level));
- // Try our best to reset all settings possible, and once finished
- // rethrow any exception that we encountered
- Exception res = null;
- switch (level) {
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- break;
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- break;
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- break;
- case LEVEL_WARM_REBOOT:
- executeWarmReboot(context, level, failedPackage);
- break;
- case LEVEL_FACTORY_RESET:
- // Before the completion of Reboot, if any crash happens then PackageWatchdog
- // escalates to next level i.e. factory reset, as they happen in separate threads.
- // Adding a check to prevent factory reset to execute before above reboot completes.
- // Note: this reboot property is not persistent resets after reboot is completed.
- if (isRebootPropertySet()) {
- return;
- }
- executeFactoryReset(context, level, failedPackage);
- break;
- }
-
- if (res != null) {
- throw res;
- }
- }
-
- private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level,
- @Nullable String failedPackage) throws Exception {
- CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
- level, levelToString(level));
- switch (level) {
- case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
- break;
- case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- break;
- case RESCUE_LEVEL_WARM_REBOOT:
- executeWarmReboot(context, level, failedPackage);
- break;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
- level);
- }
- break;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES,
- level);
- }
- break;
- case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS,
- level);
- }
- break;
- case RESCUE_LEVEL_FACTORY_RESET:
- // Before the completion of Reboot, if any crash happens then PackageWatchdog
- // escalates to next level i.e. factory reset, as they happen in separate threads.
- // Adding a check to prevent factory reset to execute before above reboot completes.
- // Note: this reboot property is not persistent resets after reboot is completed.
- if (isRebootPropertySet()) {
- return;
- }
- executeFactoryReset(context, level, failedPackage);
- break;
- }
- }
-
- private static void executeWarmReboot(Context context, int level,
- @Nullable String failedPackage) {
- if (Flags.deprecateFlagsAndSettingsResets()) {
- if (shouldThrottleReboot()) {
- return;
- }
- }
-
- // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
- // when device shutting down.
- setRebootProperty(true);
- Runnable runnable = () -> {
- try {
- PowerManager pm = context.getSystemService(PowerManager.class);
- if (pm != null) {
- pm.reboot(TAG);
- }
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- }
-
- private static void executeFactoryReset(Context context, int level,
- @Nullable String failedPackage) {
- if (Flags.deprecateFlagsAndSettingsResets()) {
- if (shouldThrottleReboot()) {
- return;
- }
- }
- setFactoryResetProperty(true);
- long now = System.currentTimeMillis();
- setLastFactoryResetTimeMs(now);
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- try {
- RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage);
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- }
-
-
- private static String getCompleteMessage(Throwable t) {
- final StringBuilder builder = new StringBuilder();
- builder.append(t.getMessage());
- while ((t = t.getCause()) != null) {
- builder.append(": ").append(t.getMessage());
- }
- return builder.toString();
- }
-
- private static void logRescueException(int level, @Nullable String failedPackageName,
- Throwable t) {
- final String msg = getCompleteMessage(t);
- EventLogTags.writeRescueFailure(level, msg);
- String failureMsg = "Failed rescue level " + levelToString(level);
- if (!TextUtils.isEmpty(failedPackageName)) {
- failureMsg += " for package " + failedPackageName;
- }
- logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
- }
-
- private static int mapRescueLevelToUserImpact(int rescueLevel) {
- if (Flags.recoverabilityDetection()) {
- switch (rescueLevel) {
- case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
- case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
- case RESCUE_LEVEL_WARM_REBOOT:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75;
- case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80;
- case RESCUE_LEVEL_FACTORY_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
- default:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- } else {
- switch (rescueLevel) {
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- case LEVEL_WARM_REBOOT:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
- case LEVEL_FACTORY_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
- default:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- }
- }
-
- private static void resetAllSettingsIfNecessary(Context context, int mode,
- int level) throws Exception {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- // No need to reset Settings again if they are already reset in the current level once.
- if (getMaxRescueLevelAttempted() >= level) {
- return;
- }
- setMaxRescueLevelAttempted(level);
- // Try our best to reset all settings possible, and once finished
- // rethrow any exception that we encountered
- Exception res = null;
- final ContentResolver resolver = context.getContentResolver();
- try {
- Settings.Global.resetToDefaultsAsUser(resolver, null, mode,
- UserHandle.SYSTEM.getIdentifier());
- } catch (Exception e) {
- res = new RuntimeException("Failed to reset global settings", e);
- }
- for (int userId : getAllUserIds()) {
- try {
- Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
- } catch (Exception e) {
- res = new RuntimeException("Failed to reset secure settings for " + userId, e);
- }
- }
- if (res != null) {
- throw res;
- }
- }
- }
-
- /**
- * Handle mitigation action for package failures. This observer will be register to Package
- * Watchdog and will receive calls about package failures. This observer is persistent so it
- * may choose to mitigate failures for packages it has not explicitly asked to observe.
- */
- public static class RescuePartyObserver implements PackageHealthObserver {
-
- private final Context mContext;
- private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>();
- private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>();
-
- @GuardedBy("RescuePartyObserver.class")
- static RescuePartyObserver sRescuePartyObserver;
-
- private RescuePartyObserver(Context context) {
- mContext = context;
- }
-
- /** Creates or gets singleton instance of RescueParty. */
- public static RescuePartyObserver getInstance(Context context) {
- synchronized (RescuePartyObserver.class) {
- if (sRescuePartyObserver == null) {
- sRescuePartyObserver = new RescuePartyObserver(context);
- }
- return sRescuePartyObserver;
- }
- }
-
- /** Gets singleton instance. It returns null if the instance is not created yet.*/
- @Nullable
- public static RescuePartyObserver getInstanceIfCreated() {
- synchronized (RescuePartyObserver.class) {
- return sRescuePartyObserver;
- }
- }
-
- @VisibleForTesting
- static void reset() {
- synchronized (RescuePartyObserver.class) {
- sRescuePartyObserver = null;
- }
- }
-
- @Override
- public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
- @FailureReasons int failureReason, int mitigationCount) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
- || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- mayPerformReboot(failedPackage), failedPackage));
- } else {
- impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
- }
- } else {
- impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- mayPerformReboot(failedPackage)));
- }
- }
-
- Slog.i(TAG, "Checking available remediations for health check failure."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " failureReason: " + failureReason
- + " available impact: " + impact);
- return impact;
- }
-
- @Override
- public boolean execute(@Nullable VersionedPackage failedPackage,
- @FailureReasons int failureReason, int mitigationCount) {
- if (isDisabled()) {
- return false;
- }
- Slog.i(TAG, "Executing remediation."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " failureReason: " + failureReason
- + " mitigationCount: " + mitigationCount);
- if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
- || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
- final int level;
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage),
- failedPackage);
- } else {
- level = getRescueLevel(mitigationCount);
- }
- } else {
- level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage));
- }
- executeRescueLevel(mContext,
- failedPackage == null ? null : failedPackage.getPackageName(), level);
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public boolean isPersistent() {
- return true;
- }
-
- @Override
- public boolean mayObservePackage(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- // A package is a module if this is non-null
- if (pm.getModuleInfo(packageName, 0) != null) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
- }
-
- return isPersistentSystemApp(packageName);
- }
-
- @Override
- public int onBootLoop(int mitigationCount) {
- if (isDisabled()) {
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- true, /*failedPackage=*/ null));
- } else {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
- }
- } else {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
- }
- }
-
- @Override
- public boolean executeBootLoopMitigation(int mitigationCount) {
- if (isDisabled()) {
- return false;
- }
- boolean mayPerformReboot = !shouldThrottleReboot();
- final int level;
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- level = getRescueLevel(mitigationCount, mayPerformReboot,
- /*failedPackage=*/ null);
- } else {
- level = getRescueLevel(mitigationCount);
- }
- } else {
- level = getRescueLevel(mitigationCount, mayPerformReboot);
- }
- executeRescueLevel(mContext, /*failedPackage=*/ null, level);
- return true;
- }
-
- @Override
- public String getUniqueIdentifier() {
- return NAME;
- }
-
- /**
- * Returns {@code true} if the failing package is non-null and performing a reboot or
- * prompting a factory reset is an acceptable mitigation strategy for the package's
- * failure, {@code false} otherwise.
- */
- private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
- if (failingPackage == null) {
- return false;
- }
- if (shouldThrottleReboot()) {
- return false;
- }
-
- return isPersistentSystemApp(failingPackage.getPackageName());
- }
-
- private boolean isPersistentSystemApp(@NonNull String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
- @NonNull String namespace) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- // Record it in calling packages to namespace map
- Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage);
- if (namespaceSet == null) {
- namespaceSet = new ArraySet<>();
- mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet);
- }
- namespaceSet.add(namespace);
- // Record it in namespace to calling packages map
- Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace);
- if (callingPackageSet == null) {
- callingPackageSet = new ArraySet<>();
- }
- callingPackageSet.add(callingPackage);
- mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet);
- }
- }
-
- private synchronized Set<String> getAffectedNamespaceSet(String failedPackage) {
- return mCallingPackageNamespaceSetMap.get(failedPackage);
- }
-
- private synchronized Set<String> getAllAffectedNamespaceSet() {
- return new HashSet<String>(mNamespaceCallingPackageSetMap.keySet());
- }
-
- private synchronized Set<String> getCallingPackagesSet(String namespace) {
- return mNamespaceCallingPackageSetMap.get(namespace);
- }
- }
-
- /**
- * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
- * Will return {@code false} if a factory reset was already offered recently.
- */
- private static boolean shouldThrottleReboot() {
- Long lastResetTime = getLastFactoryResetTimeMs();
- long now = System.currentTimeMillis();
- long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
- DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
- return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
- }
-
- private static int[] getAllUserIds() {
- int systemUserId = UserHandle.SYSTEM.getIdentifier();
- int[] userIds = { systemUserId };
- try {
- for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) {
- try {
- final int userId = Integer.parseInt(file.getName());
- if (userId != systemUserId) {
- userIds = ArrayUtils.appendInt(userIds, userId);
- }
- } catch (NumberFormatException ignored) {
- }
- }
- } catch (Throwable t) {
- Slog.w(TAG, "Trouble discovering users", t);
- }
- return userIds;
- }
-
- /**
- * Hacky test to check if the device has an active USB connection, which is
- * a good proxy for someone doing local development work.
- */
- private static boolean isUsbActive() {
- if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
- Slog.v(TAG, "Assuming virtual device is connected over USB");
- return true;
- }
- try {
- final String state = FileUtils
- .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
- return "CONFIGURED".equals(state.trim());
- } catch (Throwable t) {
- Slog.w(TAG, "Failed to determine if device was on USB", t);
- return false;
- }
- }
-
- private static class Level {
- static int none() {
- return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE;
- }
-
- static int reboot() {
- return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT;
- }
-
- static int factoryReset() {
- return Flags.recoverabilityDetection()
- ? RESCUE_LEVEL_FACTORY_RESET
- : LEVEL_FACTORY_RESET;
- }
- }
-
- private static String levelToString(int level) {
- if (Flags.recoverabilityDetection()) {
- switch (level) {
- case RESCUE_LEVEL_NONE:
- return "NONE";
- case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
- return "SCOPED_DEVICE_CONFIG_RESET";
- case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- return "ALL_DEVICE_CONFIG_RESET";
- case RESCUE_LEVEL_WARM_REBOOT:
- return "WARM_REBOOT";
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return "RESET_SETTINGS_UNTRUSTED_CHANGES";
- case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- return "RESET_SETTINGS_TRUSTED_DEFAULTS";
- case RESCUE_LEVEL_FACTORY_RESET:
- return "FACTORY_RESET";
- default:
- return Integer.toString(level);
- }
- } else {
- switch (level) {
- case LEVEL_NONE:
- return "NONE";
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return "RESET_SETTINGS_UNTRUSTED_CHANGES";
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- return "RESET_SETTINGS_TRUSTED_DEFAULTS";
- case LEVEL_WARM_REBOOT:
- return "WARM_REBOOT";
- case LEVEL_FACTORY_RESET:
- return "FACTORY_RESET";
- default:
- return Integer.toString(level);
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/ServiceThread.java b/services/core/java/com/android/server/ServiceThread.java
deleted file mode 100644
index 6d8e49c7c869..000000000000
--- a/services/core/java/com/android/server/ServiceThread.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-import android.os.StrictMode;
-
-/**
- * Special handler thread that we create for system services that require their own loopers.
- */
-public class ServiceThread extends HandlerThread {
- private static final String TAG = "ServiceThread";
-
- private final boolean mAllowIo;
-
- public ServiceThread(String name, int priority, boolean allowIo) {
- super(name, priority);
- mAllowIo = allowIo;
- }
-
- @Override
- public void run() {
- Process.setCanSelfBackground(false);
-
- if (!mAllowIo) {
- StrictMode.initThreadDefaults(null);
- }
-
- super.run();
- }
-
- protected static Handler makeSharedHandler(Looper looper) {
- return new Handler(looper, /*callback=*/ null, /* async=*/ false, /* shared=*/ true);
- }
-}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ebaf6bc95a5e..350ecab1dd5f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -97,6 +97,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -222,7 +223,7 @@ class StorageManagerService extends IStorageManager.Stub
public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
/** Extended timeout for the system server watchdog. */
- private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 20 * 1000;
+ private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 30 * 1000;
/** Extended timeout for the system server watchdog for vold#partition operation. */
private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
@@ -3259,7 +3260,7 @@ class StorageManagerService extends IStorageManager.Stub
if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
throw new SecurityException("no permission to commit checkpoint changes");
}
-
+ extendWatchdogTimeout("vold#commitChanges might be slow");
mVold.commitChanges();
}
@@ -3660,10 +3661,16 @@ class StorageManagerService extends IStorageManager.Stub
return mInternalStorageSize;
}
- @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@Override
public int getInternalStorageRemainingLifetime() throws RemoteException {
- super.getInternalStorageRemainingLifetime_enforcePermission();
+ PermissionEnforcer.fromContext(mContext)
+ .enforcePermissionAnyOf(
+ new String[] {
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.ALLOCATE_AGGRESSIVE
+ },
+ getCallingPid(),
+ getCallingUid());
return mVold.getStorageRemainingLifetime();
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 8c83ad70625a..03d6c8b695a0 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -47,6 +47,7 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.pm.RoSystemFeatures;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.build.UnboundedSdkLevel;
import com.android.server.pm.permission.PermissionAllowlist;
@@ -780,10 +781,8 @@ public class SystemConfig {
return;
}
// Read configuration of features, libs and priv-app permissions from apex module.
- int apexPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS;
- if (android.permission.flags.Flags.apexSignaturePermissionAllowlistEnabled()) {
- apexPermissionFlag |= ALLOW_SIGNATURE_PERMISSIONS;
- }
+ int apexPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
+ | ALLOW_SIGNATURE_PERMISSIONS;
// TODO: Use a solid way to filter apex module folders?
for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
if (f.isFile() || f.getPath().contains("@")) {
@@ -2021,6 +2020,13 @@ public class SystemConfig {
private void readSplitPermission(XmlPullParser parser, File permFile)
throws IOException, XmlPullParserException {
+ // If trunkstable feature flag disabled for this split permission, skip this tag.
+ if (ParsingPackageUtils.getAconfigFlags()
+ .skipCurrentElement(/* pkg= */ null, parser, /* allowNoNamespace= */ true)) {
+ XmlUtils.skipCurrentTag(parser);
+ return;
+ }
+
String splitPerm = parser.getAttributeValue(null, "name");
if (splitPerm == null) {
Slog.w(TAG, "<split-permission> without name in " + permFile + " at "
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a459ea944008..8da835896bd3 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -114,6 +114,9 @@
"options": [
{
"include-filter": "android.os.storage.cts.StorageManagerTest"
+ },
+ {
+ "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
}
]
}
@@ -175,13 +178,8 @@
]
},
{
- "name": "CtsOsTestCases",
- "file_patterns": ["StorageManagerService\\.java"],
- "options": [
- {
- "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
- }
- ]
+ "name": "FrameworksMockingServicesTests_service_batteryServiceTest",
+ "file_patterns": ["BatteryService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 744227760d95..bd7a0ac55117 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -65,9 +65,11 @@ import android.telephony.CellSignalStrengthLte;
import android.telephony.CellSignalStrengthNr;
import android.telephony.CellSignalStrengthTdscdma;
import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.CellularIdentifierDisclosure;
import android.telephony.DisconnectCause;
import android.telephony.LinkCapacityEstimate;
import android.telephony.LocationAccessPolicy;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneCapability;
import android.telephony.PhoneStateListener;
import android.telephony.PhysicalChannelConfig;
@@ -75,6 +77,7 @@ import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
import android.telephony.PreciseDisconnectCause;
import android.telephony.Rlog;
+import android.telephony.SecurityAlgorithmUpdate;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
@@ -87,9 +90,11 @@ import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.LocalLog;
import android.util.Pair;
import android.util.SparseArray;
@@ -100,6 +105,7 @@ import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.flags.Flags;
@@ -124,6 +130,7 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
@@ -162,6 +169,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
ICarrierPrivilegesCallback carrierPrivilegesCallback;
ICarrierConfigChangeListener carrierConfigChangeListener;
+ ISatelliteStateChangeListener satelliteStateChangeListener;
int callerUid;
int callerPid;
@@ -194,6 +202,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
return carrierConfigChangeListener != null;
}
+ boolean matchSatelliteStateChangeListener() {
+ return satelliteStateChangeListener != null;
+ }
+
boolean canReadCallLog() {
try {
return TelephonyPermissions.checkReadCallLog(
@@ -213,6 +225,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
+ onOpportunisticSubscriptionsChangedListenerCallback
+ " carrierPrivilegesCallback=" + carrierPrivilegesCallback
+ " carrierConfigChangeListener=" + carrierConfigChangeListener
+ + " satelliteStateChangeListener=" + satelliteStateChangeListener
+ " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
}
}
@@ -422,13 +435,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int[] mSimultaneousCellularCallingSubIds = {};
private int[] mECBMReason;
- private boolean[] mECBMStarted;
+ private long[] mECBMDuration;
private int[] mSCBMReason;
- private boolean[] mSCBMStarted;
+ private long[] mSCBMDuration;
private boolean[] mCarrierRoamingNtnMode = null;
private boolean[] mCarrierRoamingNtnEligible = null;
+ private List<IntArray> mCarrierRoamingNtnAvailableServices;
+ private NtnSignalStrength[] mCarrierRoamingNtnSignalStrength;
+
+ // Local cache to check if Satellite Modem is enabled
+ private AtomicBoolean mIsSatelliteEnabled;
+ private AtomicBoolean mWasSatelliteEnabledNotified;
+
+ private final int mPid = Process.myPid();
+
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
* type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -572,7 +594,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
|| events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
|| events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)
|| events.contains(TelephonyCallback
- .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)
+ || events.contains(TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED)
+ || events.contains(TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED);
}
private static final int MSG_USER_SWITCHED = 1;
@@ -724,11 +748,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mAllowedNetworkTypeReason = copyOf(mAllowedNetworkTypeReason, mNumPhones);
mAllowedNetworkTypeValue = copyOf(mAllowedNetworkTypeValue, mNumPhones);
mECBMReason = copyOf(mECBMReason, mNumPhones);
- mECBMStarted = copyOf(mECBMStarted, mNumPhones);
+ mECBMDuration = copyOf(mECBMDuration, mNumPhones);
mSCBMReason = copyOf(mSCBMReason, mNumPhones);
- mSCBMStarted = copyOf(mSCBMStarted, mNumPhones);
+ mSCBMDuration = copyOf(mSCBMDuration, mNumPhones);
mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones);
mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones);
+ if (mCarrierRoamingNtnSignalStrength != null) {
+ mCarrierRoamingNtnSignalStrength = copyOf(
+ mCarrierRoamingNtnSignalStrength, mNumPhones);
+ } else {
+ mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[mNumPhones];
+ }
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
@@ -741,6 +771,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
cutListToSize(mCarrierServiceStates, mNumPhones);
cutListToSize(mCallStateLists, mNumPhones);
cutListToSize(mMediaQualityStatus, mNumPhones);
+ cutListToSize(mCarrierRoamingNtnAvailableServices, mNumPhones);
return;
}
@@ -784,11 +815,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
mCarrierServiceStates.add(i, new Pair<>(null, Process.INVALID_UID));
mECBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
- mECBMStarted[i] = false;
+ mECBMDuration[i] = 0;
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
- mSCBMStarted[i] = false;
+ mSCBMDuration[i] = 0;
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
+ mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+ mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+ NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
}
}
}
@@ -859,11 +893,15 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCarrierPrivilegeStates = new ArrayList<>();
mCarrierServiceStates = new ArrayList<>();
mECBMReason = new int[numPhones];
- mECBMStarted = new boolean[numPhones];
+ mECBMDuration = new long[numPhones];
mSCBMReason = new int[numPhones];
- mSCBMStarted = new boolean[numPhones];
+ mSCBMDuration = new long[numPhones];
mCarrierRoamingNtnMode = new boolean[numPhones];
mCarrierRoamingNtnEligible = new boolean[numPhones];
+ mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+ mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[numPhones];
+ mIsSatelliteEnabled = new AtomicBoolean();
+ mWasSatelliteEnabledNotified = new AtomicBoolean();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -904,11 +942,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
mCarrierServiceStates.add(i, new Pair<>(null, Process.INVALID_UID));
mECBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
- mECBMStarted[i] = false;
+ mECBMDuration[i] = 0;
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
- mSCBMStarted[i] = false;
+ mSCBMDuration[i] = 0;
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
+ mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+ mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+ NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1402,7 +1443,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
+ if (Flags.passCopiedCallStateList()) {
+ List<CallState> callList;
+ if (r.callerPid == mPid) {
+ callList = List.copyOf(mCallStateLists.get(r.phoneId));
+ } else {
+ callList = mCallStateLists.get(r.phoneId);
+ }
+ r.callback.onCallStatesChanged(callList);
+ } else {
+ r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1493,24 +1544,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)) {
try {
- boolean ecbmStarted = mECBMStarted[r.phoneId];
- if (ecbmStarted) {
- r.callback.onCallBackModeStarted(
- TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL);
+ if (mECBMDuration[r.phoneId] != 0) {
+ r.callback.onCallbackModeStarted(
+ TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL,
+ mECBMDuration[r.phoneId], r.subId);
} else {
- r.callback.onCallBackModeStopped(
+ r.callback.onCallbackModeStopped(
TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL,
- mECBMReason[r.phoneId]);
+ mECBMReason[r.phoneId], r.subId);
}
- boolean scbmStarted = mSCBMStarted[r.phoneId];
- if (scbmStarted) {
- r.callback.onCallBackModeStarted(
- TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS);
+ if (mSCBMReason[r.phoneId] != 0) {
+ r.callback.onCallbackModeStarted(
+ TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS,
+ mSCBMDuration[r.phoneId], r.subId);
} else {
- r.callback.onCallBackModeStopped(
+ r.callback.onCallbackModeStopped(
TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS,
- mSCBMReason[r.phoneId]);
+ mSCBMReason[r.phoneId], r.subId);
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1533,6 +1584,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnAvailableServicesChanged(
+ mCarrierRoamingNtnAvailableServices.get(r.phoneId).toArray());
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnSignalStrengthChanged(
+ mCarrierRoamingNtnSignalStrength[r.phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
}
@@ -2109,7 +2178,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
}
boolean isRoaming = telephonyDisplayInfo.isRoaming();
- return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
+ boolean isNtn = telephonyDisplayInfo.isNtn();
+ boolean isSatelliteConstrainedData =
+ telephonyDisplayInfo.isSatelliteConstrainedData();
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming,
+ isNtn, isSatelliteConstrainedData);
}
public void notifyCallForwardingChanged(boolean cfi) {
@@ -2512,12 +2585,25 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (notifyCallState) {
+ List<CallState> copyList = null;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
+ // If listener is in the same process, original instance can be passed
+ // to the listener via AIDL without serialization/de-serialization. We
+ // will pass the copied list. Since the element is newly created instead
+ // of modification for the change, we can use shallow copy for this.
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ if (Flags.passCopiedCallStateList()) {
+ if (r.callerPid == mPid && copyList == null) {
+ copyList = List.copyOf(mCallStateLists.get(phoneId));
+ }
+ r.callback.onCallStatesChanged(copyList == null
+ ? mCallStateLists.get(phoneId) : copyList);
+ } else {
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ }
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2845,13 +2931,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
log("There is no active call to report CallQuality");
return;
}
-
+ List<CallState> copyList = null;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ if (Flags.passCopiedCallStateList()) {
+ if (r.callerPid == mPid && copyList == null) {
+ copyList = List.copyOf(mCallStateLists.get(phoneId));
+ }
+ r.callback.onCallStatesChanged(copyList == null
+ ? mCallStateLists.get(phoneId) : copyList);
+ } else {
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ }
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -3408,6 +3502,94 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
@Override
+ public void addSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+ @NonNull String pkg, @Nullable String featureId) {
+ final int callerUserId = UserHandle.getCallingUserId();
+ mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, featureId,
+ "addSatelliteStateChangeListener");
+ if (VDBG) {
+ log("addSatelliteStateChangeListener pkg=" + pii(pkg)
+ + " uid=" + Binder.getCallingUid()
+ + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+ + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+ }
+
+ synchronized (mRecords) {
+ final IBinder b = listener.asBinder();
+ boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+ Process.myUid());
+ Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+ if (r == null) {
+ loge("addSatelliteStateChangeListener: can not create Record instance!");
+ return;
+ }
+
+ r.context = mContext;
+ r.satelliteStateChangeListener = listener;
+ r.callingPackage = pkg;
+ r.callingFeatureId = featureId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
+ r.eventList = new ArraySet<>();
+ if (DBG) {
+ log("addSatelliteStateChangeListener: Register r=" + r);
+ }
+
+ // Always notify registrants on registration if it has been notified before
+ if (mWasSatelliteEnabledNotified.get() && r.matchSatelliteStateChangeListener()) {
+ try {
+ r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(
+ mIsSatelliteEnabled.get());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+ @NonNull String pkg) {
+ if (DBG) log("removeSatelliteStateChangeListener listener=" + listener + ", pkg=" + pkg);
+ mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, null,
+ "removeSatelliteStateChangeListener");
+ remove(listener.asBinder());
+ }
+
+ @Override
+ public void notifySatelliteStateChanged(boolean isEnabled) {
+ if (!checkNotifyPermission("notifySatelliteStateChanged")) {
+ loge("notifySatelliteStateChanged: Caller has no notify permission!");
+ return;
+ }
+ if (VDBG) {
+ log("notifySatelliteStateChanged: isEnabled=" + isEnabled);
+ }
+
+ mWasSatelliteEnabledNotified.set(true);
+ mIsSatelliteEnabled.set(isEnabled);
+
+ synchronized (mRecords) {
+ mRemoveList.clear();
+ for (Record r : mRecords) {
+ // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+ if (!r.matchSatelliteStateChangeListener()) {
+ continue;
+ }
+ try {
+ r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(isEnabled);
+ } catch (RemoteException re) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ @Override
public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) {
if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) {
return;
@@ -3457,10 +3639,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
@Override
- public void notifyCallbackModeStarted(int phoneId, int subId, int type) {
- if (!checkNotifyPermission("notifyCallbackModeStarted()")) {
- return;
- }
+ public void notifyCallbackModeStarted(int phoneId, int subId, int type, long durationMillis) {
+ if (!checkNotifyPermission("notifyCallbackModeStarted()")) return;
+
if (VDBG) {
log("notifyCallbackModeStarted: phoneId=" + phoneId + ", subId=" + subId
+ ", type=" + type);
@@ -3468,9 +3649,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
if (type == TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL) {
- mECBMStarted[phoneId] = true;
+ mECBMDuration[phoneId] = durationMillis;
} else if (type == TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS) {
- mSCBMStarted[phoneId] = true;
+ mSCBMDuration[phoneId] = durationMillis;
}
}
for (Record r : mRecords) {
@@ -3478,7 +3659,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)) {
try {
- r.callback.onCallBackModeStarted(type);
+ r.callback.onCallbackModeStarted(type, durationMillis, subId);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -3489,10 +3670,41 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
@Override
- public void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason) {
- if (!checkNotifyPermission("notifyCallbackModeStopped()")) {
- return;
+ public void notifyCallbackModeRestarted(int phoneId, int subId, int type,
+ long durationMillis) {
+ if (!checkNotifyPermission("notifyCallbackModeRestarted()")) return;
+
+ if (VDBG) {
+ log("notifyCallbackModeRestarted: phoneId=" + phoneId + ", subId=" + subId
+ + ", type=" + type);
}
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ if (type == TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL) {
+ mECBMDuration[phoneId] = durationMillis;
+ } else if (type == TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS) {
+ mSCBMDuration[phoneId] = durationMillis;
+ }
+ }
+ for (Record r : mRecords) {
+ // Send to all listeners regardless of subscription
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+ try {
+ r.callback.onCallbackModeRestarted(type, durationMillis, subId);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+
+ @Override
+ public void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason) {
+ if (!checkNotifyPermission("notifyCallbackModeStopped()")) return;
+
if (VDBG) {
log("notifyCallbackModeStopped: phoneId=" + phoneId + ", subId=" + subId
+ ", type=" + type + ", reason=" + reason);
@@ -3500,11 +3712,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
if (type == TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL) {
- mECBMStarted[phoneId] = false;
mECBMReason[phoneId] = reason;
+ mECBMDuration[phoneId] = 0;
} else if (type == TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS) {
- mSCBMStarted[phoneId] = false;
mSCBMReason[phoneId] = reason;
+ mSCBMDuration[phoneId] = 0;
}
}
for (Record r : mRecords) {
@@ -3512,7 +3724,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)) {
try {
- r.callback.onCallBackModeStopped(type, reason);
+ r.callback.onCallbackModeStopped(type, reason, subId);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -3612,6 +3824,176 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
+ /**
+ * Notify external listeners that carrier roaming non-terrestrial available services changed.
+ * @param availableServices The list of the supported services.
+ */
+ public void notifyCarrierRoamingNtnAvailableServicesChanged(
+ int subId, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnEligibleStateChanged")) {
+ log("notifyCarrierRoamingNtnAvailableServicesChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnAvailableServicesChanged: "
+ + "availableServices=" + Arrays.toString(availableServices));
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ if (!validatePhoneId(phoneId)) {
+ loge("Invalid phone ID " + phoneId + " for " + subId);
+ return;
+ }
+ IntArray availableServicesIntArray = new IntArray(availableServices.length);
+ availableServicesIntArray.addAll(availableServices);
+ mCarrierRoamingNtnAvailableServices.set(phoneId, availableServicesIntArray);
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnAvailableServicesChanged(availableServices);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ /**
+ * Notify external listeners that carrier roaming non-terrestrial network
+ * signal strength changed.
+ * @param subId subscription ID.
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ */
+ public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) {
+ log("notifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnSignalStrengthChanged: "
+ + "subId=" + subId + " ntnSignalStrength=" + ntnSignalStrength.getLevel());
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ mCarrierRoamingNtnSignalStrength[phoneId] = ntnSignalStrength;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ /**
+ * Notify that the radio security algorithms have changed.
+ *
+ * @param phoneId the phone id.
+ * @param subId the subId.
+ * @param update the security algorithm update.
+ */
+ public void notifySecurityAlgorithmsChanged(int phoneId, int subId,
+ SecurityAlgorithmUpdate update) {
+ if (!Flags.securityAlgorithmsUpdateIndications()) {
+ log("Not available due to securityAlgorithmsUpdateIndications() flag");
+ return;
+ }
+ if (!checkNotifyPermission("notifySecurityAlgorithmChanged()")) {
+ return;
+ }
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ if (update == null) {
+ loge("SecurityAlgorithmUpdate is null, subId=" + subId
+ + ", phoneId=" + phoneId);
+ // Listeners shouldn't be updated for null updates.
+ return;
+ }
+
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ if (VDBG) {
+ log("notifySecurityAlgorithmsChanged: securityAlgorithmUpdate= "
+ + update);
+ }
+ r.callback.onSecurityAlgorithmsChanged(update);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ /**
+ * Notify of a cellular identifier disclosure.
+ *
+ * @param phoneId the phone id.
+ * @param subId the subId.
+ * @param disclosure the cellular identifier disclosure.
+ */
+ public void notifyCellularIdentifierDisclosedChanged(int phoneId, int subId,
+ @NonNull CellularIdentifierDisclosure disclosure) {
+ if (!Flags.cellularIdentifierDisclosureIndications()) {
+ log("Not available due to cellularIdentifierDisclosureIndications() flag");
+ return;
+ }
+ if (!checkNotifyPermission("notifyCellularIdentifierDisclosedChanged()")) {
+ return;
+ }
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ if (disclosure == null) {
+ loge("CellularIdentifierDisclosure is null, subId=" + subId
+ + ", phoneId=" + phoneId);
+ // Listeners shouldn't be updated for null disclosures.
+ return;
+ }
+
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ if (VDBG) {
+ log("notifyCellularIdentifierDisclosedChanged: disclosure= "
+ + disclosure);
+ }
+ r.callback.onCellularIdentifierDisclosedChanged(disclosure);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3662,11 +4044,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
pw.println("mECBMReason=" + mECBMReason[i]);
- pw.println("mECBMStarted=" + mECBMStarted[i]);
+ pw.println("mECBMDuration=" + mECBMDuration[i]);
pw.println("mSCBMReason=" + mSCBMReason[i]);
- pw.println("mSCBMStarted=" + mSCBMStarted[i]);
+ pw.println("mSCBMDuration=" + mSCBMDuration[i]);
pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]);
pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]);
+ pw.println("mCarrierRoamingNtnSignalStrength="
+ + mCarrierRoamingNtnSignalStrength[i]);
// We need to obfuscate package names, and primitive arrays' native toString is ugly
Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
@@ -3676,6 +4060,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
Pair<String, Integer> carrierServiceState = mCarrierServiceStates.get(i);
pw.println("mCarrierServiceState=<package=" + pii(carrierServiceState.first)
+ ", uid=" + carrierServiceState.second + ">");
+ pw.println("mCarrierRoamingNtnAvailableServices="
+ + mCarrierRoamingNtnAvailableServices.get(i));
pw.decreaseIndent();
}
@@ -4532,4 +4918,32 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
return "[***, size=" + packageNames.size() + "]";
}
+
+ /**
+ * The method enforces the calling package at least has READ_BASIC_PHONE_STATE permission.
+ * That is, calling package either has READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE or Carrier
+ * Privileges on ANY active subscription, or has READ_BASIC_PHONE_STATE permission.
+ */
+ private void enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(String pkgName,
+ String featureId, String message) {
+ // Check if calling app has READ_PHONE_STATE on ANY active subscription
+ boolean hasReadPhoneState = false;
+ SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ if (sm != null) {
+ for (int subId : sm.getActiveSubscriptionIdList()) {
+ if (TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(mContext, subId,
+ pkgName, featureId, message)) {
+ hasReadPhoneState = true;
+ break;
+ }
+ }
+ }
+
+ // If yes, pass. If not, then enforce READ_BASIC_PHONE_STATE permission
+ if (!hasReadPhoneState) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.READ_BASIC_PHONE_STATE,
+ message);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
new file mode 100644
index 000000000000..70a033086261
--- /dev/null
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.tradeinmode.flags.Flags.enableTradeInMode;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OnAccountsUpdateListener;
+import android.annotation.RequiresPermission;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.ITradeInMode;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.service.persistentdata.PersistentDataBlockManager;
+import android.util.Slog;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+
+public final class TradeInModeService extends SystemService {
+ private static final String TAG = "TradeInModeService";
+
+ private static final String TIM_PROP = "persist.adb.tradeinmode";
+
+ private static final int TIM_STATE_UNSET = 0;
+
+ // adbd_tradeinmode was stopped.
+ private static final int TIM_STATE_DISABLED = -1;
+
+ // adbd_tradeinmode has started.
+ private static final int TIM_STATE_FOYER = 1;
+
+ // Full non-root adb granted; factory reset is guaranteed.
+ private static final int TIM_STATE_EVALUATION_MODE = 2;
+
+ // This file contains a single integer counter of how many boot attempts
+ // have been made since entering evaluation mode.
+ private static final String WIPE_INDICATOR_FILE = "/metadata/tradeinmode/wipe";
+
+ private final Context mContext;
+ private TradeInMode mTradeInMode;
+
+ private ConnectivityManager mConnectivityManager;
+ private ConnectivityManager.NetworkCallback mNetworkCallback = null;
+
+ private AccountManager mAccountManager;
+ private OnAccountsUpdateListener mAccountsListener = null;
+
+ public TradeInModeService(Context context) {
+ super(context);
+
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ if (!enableTradeInMode()) {
+ return;
+ }
+
+ mTradeInMode = new TradeInMode();
+ publishBinderService("tradeinmode", mTradeInMode);
+ }
+
+ @Override
+ public void onBootPhase(@BootPhase int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ final int state = getTradeInModeState();
+
+ if (isAdbEnabled() && !isDebuggable() && !isDeviceSetup()
+ && state == TIM_STATE_DISABLED) {
+ // If we fail to start trade-in mode, the persist property may linger
+ // past reboot. If we detect this, disable ADB and clear TIM state.
+ Slog.i(TAG, "Resetting trade-in mode state.");
+ SystemProperties.set(TIM_PROP, "");
+
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 0);
+ } else if (state == TIM_STATE_FOYER) {
+ // If zygote crashed or we rebooted, and TIM is still enabled, make
+ // sure it's allowed to be enabled. If it is, we need to re-add our
+ // setup completion observer.
+ if (isDeviceSetup()) {
+ stopTradeInMode();
+ } else {
+ watchForSetupCompletion();
+ watchForNetworkChange();
+ watchForAccountsCreated();
+ }
+ }
+ }
+ }
+
+ private final class TradeInMode extends ITradeInMode.Stub {
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public boolean start() {
+ mContext.enforceCallingOrSelfPermission("android.permission.ENTER_TRADE_IN_MODE",
+ "Cannot enter trade-in mode foyer");
+ final int state = getTradeInModeState();
+ if (state == TIM_STATE_FOYER) {
+ return true;
+ }
+
+ if (state != TIM_STATE_UNSET) {
+ Slog.e(TAG, "Cannot enter trade-in mode in state: " + state);
+ return false;
+ }
+
+ if (isDeviceSetup()) {
+ Slog.i(TAG, "Not starting trade-in mode, device is setup.");
+ return false;
+ }
+ if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
+ // We don't want to force adbd into TIM on debug builds.
+ Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
+ return false;
+ }
+ if (isAdbEnabled()) {
+ Slog.e(TAG, "Not starting trade-in mode, adb is already enabled.");
+ return false;
+ }
+
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ startTradeInMode();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ return true;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public boolean enterEvaluationMode() {
+ mContext.enforceCallingOrSelfPermission("android.permission.ENTER_TRADE_IN_MODE",
+ "Cannot enter trade-in evaluation mode");
+ final int state = getTradeInModeState();
+ if (state != TIM_STATE_FOYER) {
+ Slog.e(TAG, "Cannot enter evaluation mode in state: " + state);
+ return false;
+ }
+ if (isFrpActive()) {
+ Slog.e(TAG, "Cannot enter evaluation mode, FRP lock is present.");
+ return false;
+ }
+
+ try (FileWriter fw = new FileWriter(WIPE_INDICATOR_FILE,
+ StandardCharsets.US_ASCII)) {
+ fw.write("0");
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write " + WIPE_INDICATOR_FILE, e);
+ return false;
+ }
+
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ removeNetworkWatch();
+ removeAccountsWatch();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+
+ SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_EVALUATION_MODE));
+ SystemProperties.set("ctl.restart", "adbd");
+ return true;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public boolean isEvaluationModeAllowed() {
+ mContext.enforceCallingOrSelfPermission("android.permission.ENTER_TRADE_IN_MODE",
+ "Cannot test for trade-in evaluation mode allowed");
+ return !isFrpActive();
+ }
+ }
+
+ private void startTradeInMode() {
+ Slog.i(TAG, "Enabling trade-in mode.");
+
+ SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_FOYER));
+
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+
+ watchForSetupCompletion();
+ watchForNetworkChange();
+ watchForAccountsCreated();
+ }
+
+ private void stopTradeInMode() {
+ Slog.i(TAG, "Stopping trade-in mode.");
+
+ SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_DISABLED));
+
+ removeNetworkWatch();
+ removeAccountsWatch();
+
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 0);
+ }
+
+ private int getTradeInModeState() {
+ return SystemProperties.getInt(TIM_PROP, TIM_STATE_UNSET);
+ }
+
+ private boolean isDebuggable() {
+ return SystemProperties.getInt("ro.debuggable", 0) == 1;
+ }
+
+ private boolean isAdbEnabled() {
+ final ContentResolver cr = mContext.getContentResolver();
+ return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 1;
+ }
+
+ private boolean isFrpActive() {
+ try {
+ PersistentDataBlockManager pdb =
+ mContext.getSystemService(PersistentDataBlockManager.class);
+ if (pdb == null) {
+ return false;
+ }
+ return pdb.isFactoryResetProtectionActive();
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not read PDB", e);
+ return false;
+ }
+ }
+
+ // This returns true if the device has progressed far enough into Setup Wizard that it no
+ // longer makes sense to enable trade-in mode. As a last stop, we check the SUW completion
+ // bits.
+ private boolean isDeviceSetup() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ if (Settings.Secure.getIntForUser(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0) {
+ return true;
+ }
+ } catch (SettingNotFoundException e) {
+ Slog.e(TAG, "Could not find USER_SETUP_COMPLETE setting", e);
+ }
+
+ if (Settings.Global.getInt(cr, Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (getTradeInModeState() == TIM_STATE_FOYER && isDeviceSetup()) {
+ stopTradeInMode();
+ }
+ }
+ }
+
+ private void watchForSetupCompletion() {
+ final Uri userSetupComplete = Settings.Secure.getUriFor(
+ Settings.Secure.USER_SETUP_COMPLETE);
+ final Uri deviceProvisioned = Settings.Global.getUriFor(
+ Settings.Global.DEVICE_PROVISIONED);
+ final ContentResolver cr = mContext.getContentResolver();
+ final SettingsObserver observer = new SettingsObserver();
+
+ cr.registerContentObserver(userSetupComplete, false, observer);
+ cr.registerContentObserver(deviceProvisioned, false, observer);
+ }
+
+
+ private void watchForNetworkChange() {
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+
+ mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ super.onAvailable(network);
+ stopTradeInMode();
+ }
+ };
+
+ mConnectivityManager.registerNetworkCallback(networkRequest, mNetworkCallback);
+ }
+
+ private void removeNetworkWatch() {
+ if (mNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ mNetworkCallback = null;
+ }
+ }
+
+ private void watchForAccountsCreated() {
+ mAccountManager = mContext.getSystemService(AccountManager.class);
+ mAccountsListener = new OnAccountsUpdateListener() {
+ @Override
+ public void onAccountsUpdated(Account[] accounts) {
+ stopTradeInMode();
+ }
+ };
+ mAccountManager.addOnAccountsUpdatedListener(mAccountsListener, null, false);
+ }
+
+ private void removeAccountsWatch() {
+ if (mAccountsListener != null) {
+ mAccountManager.removeOnAccountsUpdatedListener(mAccountsListener);
+ mAccountsListener = null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f2069d011958..896c9b8d0932 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
@@ -138,7 +139,7 @@ final class UiModeManagerService extends SystemService {
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private final NightMode mNightMode = new NightMode(){
+ private final IntProperty mNightMode = new IntProperty(){
private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
@Override
@@ -192,7 +193,22 @@ final class UiModeManagerService extends SystemService {
// flag set by resource, whether to night mode change for normal all or not.
private boolean mNightModeLocked = false;
- int mCurUiMode = 0;
+ private final IntProperty mCurUiMode = new IntProperty(){
+ private int mCurrentModeTypeValue = 0;
+
+ @Override
+ public int get() {
+ return mCurrentModeTypeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mCurrentModeTypeValue = mode;
+ if (enableCurrentModeTypeBinderCache()) {
+ UiModeManager.invalidateCurrentModeTypeCache();
+ }
+ }
+ };
private int mSetUiMode = 0;
private boolean mHoldingConfiguration = false;
private int mCurrentUser;
@@ -815,7 +831,7 @@ final class UiModeManagerService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+ return mCurUiMode.get() & Configuration.UI_MODE_TYPE_MASK;
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -869,8 +885,6 @@ final class UiModeManagerService extends SystemService {
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
mNightMode.set(mode);
- //deactivates AttentionMode if user toggles DarkTheme
- mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
@@ -993,15 +1007,16 @@ final class UiModeManagerService extends SystemService {
@Override
public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) {
- return setNightModeActivatedForModeInternal(modeNightCustomType, active);
+ return setNightModeActivatedForModeInternal(modeNightCustomType, active, false);
}
@Override
public boolean setNightModeActivated(boolean active) {
- return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
+ return setNightModeActivatedForModeInternal(mNightModeCustomType, active, true);
}
- private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
+ private boolean setNightModeActivatedForModeInternal(int modeCustomType,
+ boolean active, boolean isUserInteraction) {
if (getContext().checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1037,13 +1052,16 @@ final class UiModeManagerService extends SystemService {
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
- } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
- && active) {
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO && active) {
mNightMode.set(UiModeManager.MODE_NIGHT_YES);
- } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
- && !active) {
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES && !active) {
mNightMode.set(UiModeManager.MODE_NIGHT_NO);
}
+
+ if (isUserInteraction) {
+ // deactivates AttentionMode if user toggles DarkTheme
+ mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
+ }
updateConfigurationLocked();
applyConfigurationExternallyLocked();
persistNightMode(mCurrentUser);
@@ -1499,7 +1517,7 @@ final class UiModeManagerService extends SystemService {
pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags);
pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch);
- pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
+ pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode.get()));
pw.print(" mUiModeLocked="); pw.print(mUiModeLocked);
pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
@@ -1752,7 +1770,7 @@ final class UiModeManagerService extends SystemService {
+ "; uiMode=" + uiMode);
}
- mCurUiMode = uiMode;
+ mCurUiMode.set(uiMode);
if (!mHoldingConfiguration && (!mWaitForDeviceInactive || mPowerSave)) {
mConfiguration.uiMode = uiMode;
}
@@ -1899,7 +1917,7 @@ final class UiModeManagerService extends SystemService {
boolean keepScreenOn = mCharging &&
((mCarModeEnabled && mCarModeKeepsScreenOn &&
(mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
- (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
+ (mCurUiMode.get() == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
if (keepScreenOn != mWakeLock.isHeld()) {
if (keepScreenOn) {
mWakeLock.acquire();
@@ -2055,12 +2073,14 @@ final class UiModeManagerService extends SystemService {
private void updateComputedNightModeLocked(boolean activate) {
boolean newComputedValue = activate;
+ boolean appliedOverrides = false;
if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
if (mOverrideNightModeOn && !newComputedValue) {
newComputedValue = true;
} else if (mOverrideNightModeOff && newComputedValue) {
newComputedValue = false;
}
+ appliedOverrides = true;
}
if (modesApi()) {
@@ -2070,8 +2090,10 @@ final class UiModeManagerService extends SystemService {
case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
default -> newComputedValue; // case OFF
};
- } else {
- mComputedNightMode = newComputedValue;
+ }
+
+ if (appliedOverrides) {
+ return;
}
if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
@@ -2326,11 +2348,12 @@ final class UiModeManagerService extends SystemService {
}
/**
- * Interface to contain the value for system night mode. We make the night mode accessible
- * through this class to ensure that the reassignment of this value invalidates the cache.
+ * Interface to contain the value for an integral property. We make the property
+ * accessible through this class to ensure that the reassignment of this value invalidates the
+ * cache.
*/
- private interface NightMode {
+ private interface IntProperty {
int get();
- void set(int mode);
+ void set(int value);
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 3499a3a5edde..719928b8e582 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -16,6 +16,10 @@
package com.android.server.accounts;
+import static android.Manifest.permission.COPY_ACCOUNTS;
+import static android.Manifest.permission.REMOVE_ACCOUNTS;
+import static android.app.admin.flags.Flags.splitCreateManagedProfileEnabled;
+
import android.Manifest;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
@@ -112,6 +116,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.expresslog.Histogram;
@@ -1226,7 +1231,17 @@ public class AccountManagerService
// been re-enabled (after being updated for example), then we just overwrite the old
// values.
for (Entry<String, Integer> entry : knownAuth.entrySet()) {
- accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
+ String type = entry.getKey();
+ Integer newUid = entry.getValue();
+ if (!Objects.equals(metaAuthUid.get(type), newUid)) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ type,
+ newUid,
+ FrameworkStatsLog
+ .ACCOUNT_MANAGER_EVENT__EVENT_TYPE__AUTHENTICATOR_ADDED);
+ }
+ accountsDb.insertOrReplaceMetaAuthTypeAndUid(type, newUid);
}
final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
@@ -1728,9 +1743,11 @@ public class AccountManagerService
public void copyAccountToUser(final IAccountManagerResponse response, final Account account,
final int userFrom, int userTo) {
int callingUid = Binder.getCallingUid();
- if (isCrossUser(callingUid, UserHandle.USER_ALL)) {
+ if (isCrossUser(callingUid, UserHandle.USER_ALL)
+ && !hasCopyAccountsPermission()) {
throw new SecurityException("Calling copyAccountToUser requires "
- + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ + " or " + COPY_ACCOUNTS);
}
final UserAccounts fromAccounts = getUserAccounts(userFrom);
final UserAccounts toAccounts = getUserAccounts(userTo);
@@ -1782,6 +1799,12 @@ public class AccountManagerService
}
}
+ private boolean hasCopyAccountsPermission() {
+ return splitCreateManagedProfileEnabled()
+ && mContext.checkCallingOrSelfPermission(COPY_ACCOUNTS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public boolean accountAuthenticated(final Account account) {
final int callingUid = Binder.getCallingUid();
@@ -1945,6 +1968,11 @@ public class AccountManagerService
}
accounts.accountsDb.setTransactionSuccessful();
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ account.type,
+ callingUid,
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__ACCOUNT_ADDED);
logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
accountId,
accounts, callingUid);
@@ -2330,7 +2358,8 @@ public class AccountManagerService
UserHandle user = UserHandle.of(userId);
if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier())
&& !isSystemUid(callingUid)
- && !isProfileOwner(callingUid)) {
+ && !isProfileOwner(callingUid)
+ && !hasRemoveAccountsPermission()) {
String msg = String.format(
"uid %s cannot remove accounts of type: %s",
callingUid,
@@ -2392,6 +2421,12 @@ public class AccountManagerService
}
}
+ private boolean hasRemoveAccountsPermission() {
+ return splitCreateManagedProfileEnabled()
+ && mContext.checkCallingOrSelfPermission(REMOVE_ACCOUNTS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public boolean removeAccountExplicitly(Account account) {
final int callingUid = Binder.getCallingUid();
@@ -2544,6 +2579,11 @@ public class AccountManagerService
}
String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
: AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ account.type,
+ callingUid,
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__ACCOUNT_REMOVED);
logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
}
}
@@ -2806,6 +2846,14 @@ public class AccountManagerService
: AccountsDb.DEBUG_ACTION_SET_PASSWORD;
logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts,
callingUid);
+
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ account.type,
+ callingUid,
+ TextUtils.isEmpty(password)
+ ? FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_REMOVED
+ : FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_CHANGED);
}
} finally {
accounts.accountsDb.endTransaction();
@@ -2872,7 +2920,7 @@ public class AccountManagerService
if (!accountExistsCache(accounts, account)) {
return;
}
- setUserdataInternal(accounts, account, key, value);
+ setUserdataInternal(accounts, account, key, value, callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -2892,7 +2940,7 @@ public class AccountManagerService
}
private void setUserdataInternal(UserAccounts accounts, Account account, String key,
- String value) {
+ String value, int callingUid) {
synchronized (accounts.dbLock) {
accounts.accountsDb.beginTransaction();
try {
@@ -2918,6 +2966,11 @@ public class AccountManagerService
AccountManager.invalidateLocalAccountUserDataCaches();
}
}
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ account.type,
+ callingUid,
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__USER_DATA_CHANGED);
}
private void onResult(IAccountManagerResponse response, Bundle result) {
@@ -5062,6 +5115,8 @@ public class AccountManagerService
Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
return false;
}
+ intent.setComponent(targetActivityInfo.getComponentName());
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return true;
} finally {
Binder.restoreCallingIdentity(bid);
@@ -5083,14 +5138,15 @@ public class AccountManagerService
Bundle simulateBundle = p.readBundle();
p.recycle();
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
- if (intent != null && intent.getClass() != Intent.class) {
- return false;
- }
Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT,
Intent.class);
if (intent == null) {
return (simulateIntent == null);
}
+ if (intent.getClass() != Intent.class || simulateIntent.getClass() != Intent.class) {
+ return false;
+ }
+
if (!intent.filterEquals(simulateIntent)) {
return false;
}
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index b18810564d88..000000000000
--- a/services/core/java/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hainingc@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f9197e3c5c42..60516c39ffa7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -115,6 +115,7 @@ import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__BIND;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__START;
+import static com.android.media.flags.Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
@@ -1189,8 +1190,8 @@ public final class ActiveServices {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
}
final boolean wasStartRequested = r.startRequested;
- r.lastActivity = SystemClock.uptimeMillis();
- r.startRequested = true;
+ mAm.mProcessStateController.setServiceLastActivityTime(r, SystemClock.uptimeMillis());
+ mAm.mProcessStateController.setStartRequested(r, true);
r.delayedStop = false;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
@@ -1623,7 +1624,7 @@ public final class ActiveServices {
FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
mAm.mBatteryStatsService.noteServiceStopRunning(uid, packageName, serviceName);
- service.startRequested = false;
+ mAm.mProcessStateController.setStartRequested(service, false);
if (service.tracker != null) {
synchronized (mAm.mProcessStats.mLock) {
service.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -1812,7 +1813,7 @@ public final class ActiveServices {
FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
mAm.mBatteryStatsService.noteServiceStopRunning(uid, packageName, serviceName);
- r.startRequested = false;
+ mAm.mProcessStateController.setStartRequested(r, false);
if (r.tracker != null) {
synchronized (mAm.mProcessStats.mLock) {
r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -2618,7 +2619,7 @@ public final class ActiveServices {
}
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
r.foregroundNoti = notification;
- r.foregroundServiceType = foregroundServiceType;
+ mAm.mProcessStateController.setForegroundServiceType(r, foregroundServiceType);
if (!r.isForeground) {
final ServiceMap smap = getServiceMapLocked(r.userId);
if (smap != null) {
@@ -2643,7 +2644,7 @@ public final class ActiveServices {
}
active.mNumActive++;
}
- r.isForeground = true;
+ mAm.mProcessStateController.setIsForegroundService(r, true);
// The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
// be deferred, make a copy of mAllowStartForeground and
@@ -2772,7 +2773,7 @@ public final class ActiveServices {
}
}
- r.isForeground = false;
+ mAm.mProcessStateController.setIsForegroundService(r, false);
r.mFgsExitTime = SystemClock.uptimeMillis();
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
@@ -3565,7 +3566,7 @@ public final class ActiveServices {
private void maybeUpdateShortFgsTrackingLocked(ServiceRecord sr,
boolean extendTimeout) {
if (!sr.isShortFgs()) {
- sr.clearShortFgsInfo(); // Just in case we have it.
+ mAm.mProcessStateController.clearShortFgsInfo(sr); // Just in case we have it.
unscheduleShortFgsTimeoutLocked(sr);
return;
}
@@ -3581,7 +3582,7 @@ public final class ActiveServices {
}
}
traceInstant("short FGS start/extend: ", sr);
- sr.setShortFgsInfo(SystemClock.uptimeMillis());
+ mAm.mProcessStateController.setShortFgsInfo(sr, SystemClock.uptimeMillis());
// We'll restart the timeout.
unscheduleShortFgsTimeoutLocked(sr);
@@ -3605,7 +3606,7 @@ public final class ActiveServices {
* Stop the timeout for a ServiceRecord, if it's of a short-FGS.
*/
private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
- sr.clearShortFgsInfo(); // Always clear, just in case.
+ mAm.mProcessStateController.clearShortFgsInfo(sr); // Always clear, just in case.
if (!sr.isShortFgs()) {
return;
}
@@ -3993,7 +3994,7 @@ public final class ActiveServices {
private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
maybeStopShortFgsTimeoutLocked(service);
final ProcessServiceRecord psr = service.app.mServices;
- psr.stopService(service);
+ mAm.mProcessStateController.stopService(psr, service);
psr.updateBoundClientUids();
if (service.allowlistManager) {
updateAllowlistManagerLocked(psr);
@@ -4047,7 +4048,7 @@ public final class ActiveServices {
}
}
if (anyClientActivities != psr.hasClientActivities()) {
- psr.setHasClientActivities(anyClientActivities);
+ mAm.mProcessStateController.setHasClientActivities(psr, anyClientActivities);
if (updateLru) {
mAm.updateLruProcessLocked(psr.mApp, anyClientActivities, null);
}
@@ -4216,7 +4217,8 @@ public final class ActiveServices {
}
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
- s.lastActivity = SystemClock.uptimeMillis();
+ mAm.mProcessStateController.setServiceLastActivityTime(s,
+ SystemClock.uptimeMillis());
if (!s.hasAutoCreateConnections()) {
// This is the first binding, let the tracker know.
synchronized (mAm.mProcessStats.mLock) {
@@ -4253,12 +4255,12 @@ public final class ActiveServices {
if (activity != null) {
activity.addConnection(c);
}
- clientPsr.addConnection(c);
+ mAm.mProcessStateController.addConnection(clientPsr, c);
c.startAssociationIfNeeded();
// Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from
// dropping the process' adjustment level.
if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
- clientPsr.setHasAboveClient(true);
+ mAm.mProcessStateController.setHasAboveClient(clientPsr, true);
}
if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) {
s.allowlistManager = true;
@@ -4274,7 +4276,8 @@ public final class ActiveServices {
if (s.app != null && s.app.mState != null
&& s.app.mState.getCurProcState() <= PROCESS_STATE_TOP
&& c.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)) {
- s.lastTopAlmostPerceptibleBindRequestUptimeMs = SystemClock.uptimeMillis();
+ mAm.mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s,
+ SystemClock.uptimeMillis());
}
if (s.app != null) {
@@ -4312,7 +4315,8 @@ public final class ActiveServices {
boolean needOomAdj = false;
if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
- s.lastActivity = SystemClock.uptimeMillis();
+ mAm.mProcessStateController.setServiceLastActivityTime(s,
+ SystemClock.uptimeMillis());
needOomAdj = (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) == 0;
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
@@ -4328,7 +4332,7 @@ public final class ActiveServices {
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
if (c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
- servicePsr.setTreatLikeActivity(true);
+ mAm.mProcessStateController.setTreatLikeActivity(servicePsr, true);
}
if (s.allowlistManager) {
servicePsr.mAllowlistManager = true;
@@ -4575,7 +4579,9 @@ public final class ActiveServices {
}
// This could have made the service less important.
if (r.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
- psr.setTreatLikeActivity(true);
+ // TODO(b/367545398): the following line is a bug. A service unbind
+ // should potentially lower a process's importance, not elevate it.
+ mAm.mProcessStateController.setTreatLikeActivity(psr, true);
mAm.updateLruProcessLocked(app, true, null);
}
// If the bindee is more important than the binder, we may skip the OomAdjuster.
@@ -4598,7 +4604,7 @@ public final class ActiveServices {
return true;
}
- void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) {
+ void unbindFinishedLocked(ServiceRecord r, Intent intent) {
final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (r != null) {
@@ -5165,8 +5171,9 @@ public final class ActiveServices {
}
if (r.app != null) {
psr = r.app.mServices;
- psr.startExecutingService(r);
- psr.setExecServicesFg(psr.shouldExecServicesFg() || fg);
+ mAm.mProcessStateController.startExecutingService(psr, r);
+ mAm.mProcessStateController.setExecServicesFg(psr,
+ psr.shouldExecServicesFg() || fg);
if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
if (!shouldSkipTimeout) {
scheduleServiceTimeoutLocked(r.app);
@@ -5178,7 +5185,7 @@ public final class ActiveServices {
} else if (r.app != null && fg) {
psr = r.app.mServices;
if (!psr.shouldExecServicesFg()) {
- psr.setExecServicesFg(true);
+ mAm.mProcessStateController.setExecServicesFg(psr, true);
if (timeoutNeeded) {
if (!shouldSkipTimeout) {
scheduleServiceTimeoutLocked(r.app);
@@ -5838,7 +5845,7 @@ public final class ActiveServices {
if (r.inSharedIsolatedProcess) {
app = mAm.mProcessList.getSharedIsolatedProcess(procName, r.appInfo.uid,
r.appInfo.packageName);
- if (app != null) {
+ if (app != null && !app.isKilled()) {
final IApplicationThread thread = app.getThread();
final int pid = app.getPid();
final UidRecord uidRecord = app.getUidRecord();
@@ -5863,6 +5870,8 @@ public final class ActiveServices {
// If a dead object exception was thrown -- fall through to
// restart the application.
}
+ } else {
+ app = null;
}
} else {
// If this service runs in an isolated process, then each time
@@ -6023,11 +6032,13 @@ public final class ActiveServices {
Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
+ ", ProcessRecord.uid = " + app.uid);
r.setProcess(app, thread, pid, uidRecord);
- r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
+ final long now = SystemClock.uptimeMillis();
+ r.restartTime = now;
+ mAm.mProcessStateController.setServiceLastActivityTime(r, now);
final boolean skipOomAdj = (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) != 0;
final ProcessServiceRecord psr = app.mServices;
- final boolean newService = psr.startService(r);
+ final boolean newService = mAm.mProcessStateController.startService(psr, r);
bumpServiceExecutingLocked(r, execInFg, "create",
OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */,
skipOomAdj /* skipTimeoutIfPossible */);
@@ -6086,7 +6097,7 @@ public final class ActiveServices {
// Cleanup.
if (newService) {
- psr.stopService(r);
+ mAm.mProcessStateController.stopService(psr, r);
r.setProcess(null, null, 0, null);
}
@@ -6431,7 +6442,7 @@ public final class ActiveServices {
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
}
- r.isForeground = false;
+ mAm.mProcessStateController.setIsForegroundService(r, false);
r.mFgsNotificationWasDeferred = false;
dropFgsNotificationStateLocked(r);
r.foregroundId = 0;
@@ -6582,9 +6593,9 @@ public final class ActiveServices {
}
if (b.client != skipApp) {
final ProcessServiceRecord psr = b.client.mServices;
- psr.removeConnection(c);
+ mAm.mProcessStateController.removeConnection(psr, c);
if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
- psr.updateHasAboveClientLocked();
+ mAm.mProcessStateController.updateHasAboveClientLocked(psr);
}
// If this connection requested allowlist management, see if we should
// now clear that state.
@@ -6600,7 +6611,7 @@ public final class ActiveServices {
}
// And for almost perceptible exceptions.
if (c.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)) {
- psr.updateHasTopStartedAlmostPerceptibleServices();
+ mAm.mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(psr);
}
if (s.app != null) {
updateServiceClientActivitiesLocked(s.app.mServices, c, true);
@@ -6799,8 +6810,8 @@ public final class ActiveServices {
final ProcessServiceRecord psr = r.app.mServices;
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"Nesting at 0 of " + r.shortInstanceName);
- psr.setExecServicesFg(false);
- psr.stopExecutingService(r);
+ mAm.mProcessStateController.setExecServicesFg(psr, false);
+ mAm.mProcessStateController.stopExecutingService(psr, r);
if (psr.numberOfExecutingServices() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortInstanceName);
@@ -6809,7 +6820,7 @@ public final class ActiveServices {
// Need to re-evaluate whether the app still needs to be in the foreground.
for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
if (psr.getExecutingServiceAt(i).executeFg) {
- psr.setExecServicesFg(true);
+ mAm.mProcessStateController.setExecServicesFg(psr, true);
break;
}
}
@@ -6822,9 +6833,9 @@ public final class ActiveServices {
}
if (oomAdjReason != OOM_ADJ_REASON_NONE) {
if (enqueueOomAdj) {
- mAm.enqueueOomAdjTargetLocked(r.app);
+ mAm.mProcessStateController.enqueueUpdateTarget(r.app);
} else {
- mAm.updateOomAdjLocked(r.app, oomAdjReason);
+ mAm.mProcessStateController.runUpdate(r.app, oomAdjReason);
}
} else {
// Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
@@ -7209,8 +7220,7 @@ public final class ActiveServices {
removeConnectionLocked(r, app, null, true);
}
updateServiceConnectionActivitiesLocked(psr);
- psr.removeAllConnections();
- psr.removeAllSdkSandboxConnections();
+ mAm.mProcessStateController.removeAllConnections(psr);
psr.mAllowlistManager = false;
@@ -7220,7 +7230,7 @@ public final class ActiveServices {
mAm.mBatteryStatsService.noteServiceStopLaunch(sr.appInfo.uid, sr.name.getPackageName(),
sr.name.getClassName());
if (sr.app != app && sr.app != null && !sr.app.isPersistent()) {
- sr.app.mServices.stopService(sr);
+ mAm.mProcessStateController.stopService(sr.app.mServices, sr);
sr.app.mServices.updateBoundClientUids();
}
sr.setProcess(null, null, 0, null);
@@ -7290,7 +7300,7 @@ public final class ActiveServices {
// Unless the process is persistent, this process record is going away,
// so make sure the service is cleaned out of it.
if (!app.isPersistent()) {
- psr.stopService(sr);
+ mAm.mProcessStateController.stopService(psr, sr);
psr.updateBoundClientUids();
}
@@ -7331,7 +7341,7 @@ public final class ActiveServices {
// Update to stopped state because the explicit start is gone. The service is
// scheduled to restart for other reason (e.g. connections) so we don't bring
// down it.
- sr.startRequested = false;
+ mAm.mProcessStateController.setStartRequested(sr, false);
if (sr.tracker != null) {
synchronized (mAm.mProcessStats.mLock) {
sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -7345,7 +7355,7 @@ public final class ActiveServices {
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (!allowRestart) {
- psr.stopAllServices();
+ mAm.mProcessStateController.stopAllServices(psr);
psr.clearBoundClientUids();
// Make sure there are no more restarting services for this process.
@@ -7387,7 +7397,7 @@ public final class ActiveServices {
}
}
- psr.stopAllExecutingServices();
+ mAm.mProcessStateController.stopAllExecutingServices(psr);
psr.noteScheduleServiceTimeoutPending(false);
}
@@ -9213,14 +9223,14 @@ public final class ActiveServices {
new ForegroundServiceDelegation(options, connection);
r.mFgsDelegation = delegation;
mFgsDelegations.put(delegation, r);
- r.isForeground = true;
+ mAm.mProcessStateController.setIsForegroundService(r, true);
r.mFgsEnterTime = SystemClock.uptimeMillis();
- r.foregroundServiceType = options.mForegroundServiceTypes;
+ mAm.mProcessStateController.setForegroundServiceType(r, options.mForegroundServiceTypes);
r.updateOomAdjSeq();
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
BackgroundStartPrivileges.NONE, false /* isBindService */);
final ProcessServiceRecord psr = callerApp.mServices;
- final boolean newService = psr.startService(r);
+ final boolean newService = mAm.mProcessStateController.startService(psr, r);
// updateOomAdj.
updateServiceForegroundLocked(psr, /* oomAdj= */ true);
@@ -9311,6 +9321,86 @@ public final class ActiveServices {
Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
}
}
+ /**
+ * Handles notifications from MediaSessionService about active media service.
+ * This method evaluates the provided information and transitions corresponding service to
+ * foreground state.
+ *
+ * @param packageName The package name of the app running the service.
+ * @param userId The user ID associated with the service.
+ * @param notificationId The ID of the media notification associated with the service.
+ */
+ void notifyActiveMediaForegroundServiceLocked(@NonNull String packageName,
+ @UserIdInt int userId, int notificationId) {
+ if (!enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+
+ final ServiceMap smap = mServiceMap.get(userId);
+ if (smap == null) {
+ return;
+ }
+ final int serviceSize = smap.mServicesByInstanceName.size();
+ for (int i = 0; i < serviceSize; i++) {
+ final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+ if (sr.appInfo.packageName.equals(packageName) && !sr.isForeground) {
+ // foregroundServiceType is cleared when media session is user-disengaged
+ // and calls notifyInactiveMediaForegroundService->setServiceForegroundInnerLocked.
+ if (sr.foregroundServiceType
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
+ && sr.foregroundId == notificationId) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG, "Moving media service to foreground for package "
+ + packageName);
+ }
+ setServiceForegroundInnerLocked(sr, sr.foregroundId,
+ sr.foregroundNoti, /* flags */ 0,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ /* callingUidStart */ 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles notifications from MediaSessionService about inactive media foreground services.
+ * This method evaluates the provided information and determines whether to stop the
+ * corresponding foreground service.
+ *
+ * @param packageName The package name of the app running the foreground service.
+ * @param userId The user ID associated with the foreground service.
+ * @param notificationId The ID of the media notification associated with the foreground
+ * service.
+ */
+ void notifyInactiveMediaForegroundServiceLocked(@NonNull String packageName,
+ @UserIdInt int userId, int notificationId) {
+ if (!enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+
+ final ServiceMap smap = mServiceMap.get(userId);
+ if (smap == null) {
+ return;
+ }
+ final int serviceSize = smap.mServicesByInstanceName.size();
+ for (int i = 0; i < serviceSize; i++) {
+ final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+ if (sr.appInfo.packageName.equals(packageName) && sr.isForeground) {
+ if (sr.foregroundServiceType
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
+ && sr.foregroundId == notificationId) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG, "Forcing media foreground service to background for package "
+ + packageName);
+ }
+ setServiceForegroundInnerLocked(sr, /* id */ 0,
+ /* notification */ null, /* flags */ 0,
+ /* foregroundServiceType */ 0, /* callingUidStart */ 0);
+ }
+ }
+ }
+ }
+
private static void getClientPackages(ServiceRecord sr, ArraySet<String> output) {
var connections = sr.getConnections();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 6fd281e7003e..4944cafeb83d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -163,6 +163,7 @@ final class ActivityManagerConstants extends ContentObserver {
static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
+ static final String KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE = "tiered_cached_adj_ui_tier_size";
/**
* Whether or not to enable the new oom adjuster implementation.
@@ -246,8 +247,10 @@ final class ActivityManagerConstants extends ContentObserver {
static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000;
- private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
+ private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = Flags.oomadjusterCachedAppTiers();
private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
+ private static final int TIERED_CACHED_ADJ_MAX_UI_TIER_SIZE = 50;
+ private final int mDefaultTieredCachedAdjUiTierSize;
/**
* The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
@@ -685,11 +688,6 @@ final class ActivityManagerConstants extends ContentObserver {
// default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
volatile boolean mForceEnablePssProfiling = false;
- // Indicates whether to use ApplicationInfo to determine launched state instead of PM user state
- // This is a temporary workaround until the trunk-stable flag is pushed to nextfood.
- // TODO: b/365979852 - remove this workaround when redundant
- volatile boolean mFlagUseAppInfoNotLaunched = false;
-
/**
* Indicates whether the foreground service background start restriction is enabled for
* caller app that is targeting S+.
@@ -1022,9 +1020,6 @@ final class ActivityManagerConstants extends ContentObserver {
private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
- private static final Uri ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI =
- Settings.Global.getUriFor(Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED);
-
/**
* The threshold to decide if a given association should be dumped into metrics.
*/
@@ -1162,6 +1157,9 @@ final class ActivityManagerConstants extends ContentObserver {
/** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */
public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME;
+ /** @see #KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE */
+ public int TIERED_CACHED_ADJ_UI_TIER_SIZE;
+
/** @see #KEY_ENABLE_NEW_OOMADJ */
public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ;
@@ -1371,6 +1369,7 @@ final class ActivityManagerConstants extends ContentObserver {
break;
case KEY_USE_TIERED_CACHED_ADJ:
case KEY_TIERED_CACHED_ADJ_DECAY_TIME:
+ case KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE:
updateUseTieredCachedAdj();
break;
case KEY_DISABLE_APP_PROFILER_PSS_PROFILING:
@@ -1474,6 +1473,11 @@ final class ActivityManagerConstants extends ContentObserver {
mDefaultPssToRssThresholdModifier = context.getResources().getFloat(
com.android.internal.R.dimen.config_am_pssToRssThresholdModifier);
PSS_TO_RSS_THRESHOLD_MODIFIER = mDefaultPssToRssThresholdModifier;
+
+ mDefaultTieredCachedAdjUiTierSize = context.getResources().getInteger(
+ com.android.internal.R.integer.config_am_tieredCachedAdjUiTierSize);
+ TIERED_CACHED_ADJ_UI_TIER_SIZE = Math.min(
+ mDefaultTieredCachedAdjUiTierSize, TIERED_CACHED_ADJ_MAX_UI_TIER_SIZE);
}
public void start(ContentResolver resolver) {
@@ -1487,7 +1491,6 @@ final class ActivityManagerConstants extends ContentObserver {
false, this);
}
mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
- mResolver.registerContentObserver(ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI, false, this);
updateConstants();
if (mSystemServerAutomaticHeapDumpEnabled) {
updateEnableAutomaticSystemServerHeapDumps();
@@ -1504,7 +1507,6 @@ final class ActivityManagerConstants extends ContentObserver {
updateActivityStartsLoggingEnabled();
updateForegroundServiceStartsLoggingEnabled();
updateForceEnablePssProfiling();
- updateEnableUseAppInfoNotLaunched();
// Read DropboxRateLimiter params from flags.
mService.initDropboxRateLimiter();
}
@@ -1550,8 +1552,6 @@ final class ActivityManagerConstants extends ContentObserver {
updateEnableAutomaticSystemServerHeapDumps();
} else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
updateForceEnablePssProfiling();
- } else if (ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI.equals(uri)) {
- updateEnableUseAppInfoNotLaunched();
}
}
@@ -1671,11 +1671,6 @@ final class ActivityManagerConstants extends ContentObserver {
Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
}
- private void updateEnableUseAppInfoNotLaunched() {
- mFlagUseAppInfoNotLaunched = Settings.Global.getInt(mResolver,
- Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, 0) == 1;
- }
-
private void updateBackgroundActivityStarts() {
mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2272,6 +2267,12 @@ final class ActivityManagerConstants extends ContentObserver {
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_TIERED_CACHED_ADJ_DECAY_TIME,
DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME);
+ TIERED_CACHED_ADJ_UI_TIER_SIZE = Math.min(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE,
+ mDefaultTieredCachedAdjUiTierSize),
+ TIERED_CACHED_ADJ_MAX_UI_TIER_SIZE);
}
private void updateEnableNewOomAdj() {
@@ -2527,6 +2528,8 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
+ pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE);
+ pw.print("="); pw.println(TIERED_CACHED_ADJ_UI_TIER_SIZE);
pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ);
pw.print("="); pw.println(ENABLE_NEW_OOMADJ);
@@ -2555,8 +2558,6 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
pw.println(mEnableWaitForFinishAttachApplication);
- pw.print(" FLAG_USE_APP_INFO_NOT_LAUNCHED=");
- pw.println(mFlagUseAppInfoNotLaunched);
pw.print(" "); pw.print(KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
pw.print("="); pw.println(FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8de1295dfe6c..ce8dd66c1011 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -130,10 +130,14 @@ import static android.os.Process.setThreadScheduler;
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static android.security.Flags.preventIntentRedirect;
+import static android.security.Flags.preventIntentRedirectCollectNestedKeysOnServerIfNotCollected;
+import static android.security.Flags.preventIntentRedirectShowToastIfNestedKeysNotCollectedRW;
+import static android.security.Flags.preventIntentRedirectThrowExceptionIfNestedKeysNotCollected;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.util.FrameworkStatsLog.INTENT_CREATOR_TOKEN_ADDED;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED;
import static com.android.sdksandbox.flags.Flags.sdkSandboxInstrumentationInfo;
import static com.android.server.am.ActiveServices.FGS_SAW_RESTRICTIONS;
@@ -188,6 +192,7 @@ import static com.android.systemui.shared.Flags.enableHomeDelay;
import android.Manifest;
import android.Manifest.permission;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PermissionMethod;
@@ -262,6 +267,7 @@ import android.appwidget.AppWidgetManagerInternal;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -335,6 +341,8 @@ import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
+import android.os.ProfilingServiceHelper;
+import android.os.ProfilingTrigger;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -353,6 +361,8 @@ import android.os.WorkSource;
import android.os.incremental.IIncrementalService;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalMetrics;
+import android.os.instrumentation.IOffsetCallback;
+import android.os.instrumentation.MethodDescriptor;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
@@ -383,6 +393,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.autofill.AutofillManagerInternal;
+import android.widget.Toast;
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
@@ -397,6 +408,7 @@ import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.ApplicationSharedMemory;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer;
@@ -412,13 +424,12 @@ import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.policy.AttributeCache;
-import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
-import com.android.server.crashrecovery.CrashRecoveryHelper;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
@@ -433,11 +444,14 @@ import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
+import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.crashrecovery.CrashRecoveryAdaptor;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.firewall.IntentFirewall;
import com.android.server.graphics.fonts.FontManagerInternal;
@@ -482,6 +496,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@@ -496,9 +511,11 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -575,7 +592,7 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
// How many seconds should the system wait before terminating the spawned logcat process.
- static final int LOGCAT_TIMEOUT_SEC = 10;
+ static final int LOGCAT_TIMEOUT_SEC = Flags.logcatLongerTimeout() ? 15 : 10;
// Necessary ApplicationInfo flags to mark an app as persistent
static final int PERSISTENT_MASK =
@@ -620,13 +637,15 @@ public class ActivityManagerService extends IActivityManager.Stub
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSZ");
OomAdjuster mOomAdjuster;
+ @GuardedBy("this")
+ ProcessStateController mProcessStateController;
static final String EXTRA_TITLE = "android.intent.extra.TITLE";
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
- static final String EXTRA_EXTRA_ATTACHMENT_URI =
- "android.intent.extra.EXTRA_ATTACHMENT_URI";
+ static final String EXTRA_EXTRA_ATTACHMENT_URIS =
+ "android.intent.extra.EXTRA_ATTACHMENT_URIS";
/**
* The maximum number of bytes that {@link #setProcessStateSummary} accepts.
@@ -658,6 +677,8 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
private static final boolean ENABLE_PROC_LOCK = true;
+ private static final int DEFAULT_INTENT_CREATOR_UID = -1;
+
/**
* The lock for process management.
*
@@ -836,6 +857,8 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
final ComponentAliasResolver mComponentAliasResolver;
+ final FileDescriptor mApplicationSharedMemoryReadOnlyFd;
+
private static final long HOME_LAUNCH_TIMEOUT_MS = 15000;
private final AtomicBoolean mHasHomeDelay = new AtomicBoolean(false);
@@ -1038,13 +1061,14 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
synchronized (this) {
- mProcessList.getAppStartInfoTracker().onIntentStarted(intent, timestampNanos);
+ mProcessList.getAppStartInfoTracker()
+ .onActivityIntentStarted(intent, timestampNanos);
}
}
@Override
public void onIntentFailed(long id) {
- mProcessList.getAppStartInfoTracker().onIntentFailed(id);
+ mProcessList.getAppStartInfoTracker().onActivityIntentFailed(id);
}
@Override
@@ -1078,7 +1102,18 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void onReportFullyDrawn(long id, long timestampNanos) {
- mProcessList.getAppStartInfoTracker().onReportFullyDrawn(id, timestampNanos);
+ ApplicationStartInfo startInfo = mProcessList.getAppStartInfoTracker()
+ .onActivityReportFullyDrawn(id, timestampNanos);
+
+ if (android.os.profiling.Flags.systemTriggeredProfilingNew()
+ && startInfo != null
+ && startInfo.getStartType() == ApplicationStartInfo.START_TYPE_COLD
+ && startInfo.getPackageName() != null) {
+ ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(
+ startInfo.getRealUid(),
+ startInfo.getPackageName(),
+ ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
+ }
}
};
@@ -1949,7 +1984,7 @@ public class ActivityManagerService extends IActivityManager.Stub
new HostingRecord(HostingRecord.HOSTING_TYPE_SYSTEM));
app.setPersistent(true);
app.setPid(MY_PID);
- app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
+ mProcessStateController.setMaxAdj(app, ProcessList.SYSTEM_ADJ);
app.makeActive(new ApplicationThreadDeferred(mSystemThread.getApplicationThread()),
mProcessStats);
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
@@ -2199,7 +2234,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mService.mBroadcastController.startBroadcastObservers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
if (!refactorCrashrecovery()) {
- mService.mPackageWatchdog.onPackagesReady();
+ CrashRecoveryAdaptor.packageWatchdogOnPackagesReady(mService.mPackageWatchdog);
} else {
mService.mCrashRecoveryHelper.registerConnectivityModuleHealthListener();
}
@@ -2385,9 +2420,11 @@ public class ActivityManagerService extends IActivityManager.Stub
mProcessList.init(this, activeUids, mPlatformCompat);
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
- ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
- : new OomAdjuster(this, mProcessList, activeUids, handlerThread);
+ mProcessStateController = new ProcessStateController.Builder(this, mProcessList, activeUids)
+ .setHandlerThread(handlerThread)
+ .useModernOomAdjuster(mConstants.ENABLE_NEW_OOMADJ)
+ .build();
+ mOomAdjuster = mProcessStateController.getOomAdjuster();
mIntentFirewall = injector.getIntentFirewall();
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
@@ -2412,6 +2449,8 @@ public class ActivityManagerService extends IActivityManager.Stub
mBroadcastQueue = injector.getBroadcastQueue(this);
mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mComponentAliasResolver = new ComponentAliasResolver(this);
+ mApplicationSharedMemoryReadOnlyFd = null;
+ sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2448,9 +2487,10 @@ public class ActivityManagerService extends IActivityManager.Stub
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(),
new LowMemDetector(this));
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
- ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
- : new OomAdjuster(this, mProcessList, activeUids);
+ mProcessStateController = new ProcessStateController.Builder(this, mProcessList, activeUids)
+ .useModernOomAdjuster(mConstants.ENABLE_NEW_OOMADJ)
+ .build();
+ mOomAdjuster = mProcessStateController.getOomAdjuster();
mBroadcastQueue = mInjector.getBroadcastQueue(this);
mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
@@ -2518,6 +2558,14 @@ public class ActivityManagerService extends IActivityManager.Stub
mPendingStartActivityUids = new PendingStartActivityUids();
mTraceErrorLogger = new TraceErrorLogger();
mComponentAliasResolver = new ComponentAliasResolver(this);
+ sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
+ try {
+ mApplicationSharedMemoryReadOnlyFd =
+ ApplicationSharedMemory.getInstance().getReadOnlyFileDescriptor();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get read only fd for shared memory", e);
+ throw new RuntimeException(e);
+ }
}
void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
@@ -4441,16 +4489,11 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "Unattached app died before backup, skipping");
final int userId = app.userId;
final String packageName = app.info.packageName;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentDisconnectedForUser(userId, packageName);
- } catch (RemoteException e) {
- // Can't happen; the backup manager is local
- }
+ mHandler.post(() -> {
+ try {
+ getBackupManager().agentDisconnectedForUser(userId, packageName);
+ } catch (RemoteException e) {
+ // Can't happen; the backup manager is local
}
});
}
@@ -4556,7 +4599,7 @@ public class ActivityManagerService extends IActivityManager.Stub
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
synchronized (mProcLock) {
- mOomAdjuster.setAttachingProcessStatesLSP(app);
+ mProcessStateController.setAttachingProcessStatesLSP(app);
clearProcessForegroundLocked(app);
app.setDebugging(false);
app.setKilledByAm(false);
@@ -4621,7 +4664,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) {
isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID
&& ((backupTarget.backupMode == BackupRecord.RESTORE_FULL)
- || (backupTarget.backupMode == BackupRecord.BACKUP_FULL));
+ || (backupTarget.backupMode == BackupRecord.BACKUP_FULL))
+ && backupTarget.useRestrictedMode;
}
final ActiveInstrumentation instr = app.getActiveInstrumentation();
@@ -4630,8 +4674,6 @@ public class ActivityManagerService extends IActivityManager.Stub
notifyPackageUse(instr.mClass.getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
}
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Binding proc %s with config %s",
- processName, app.getWindowProcessController().getConfiguration());
ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info;
ProfilerInfo profilerInfo = mAppProfiler.setupProfilerInfoLocked(thread, app, instr);
@@ -4725,6 +4767,7 @@ public class ActivityManagerService extends IActivityManager.Stub
app.getDisabledCompatChanges(),
app.getLoggableCompatChanges(),
serializedSystemFontMap,
+ mApplicationSharedMemoryReadOnlyFd,
app.getStartElapsedTime(),
app.getStartUptime());
}
@@ -4751,7 +4794,7 @@ public class ActivityManagerService extends IActivityManager.Stub
app.makeActive(new ApplicationThreadDeferred(thread), mProcessStats);
checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
}
- app.setPendingFinishAttach(true);
+ mProcessStateController.setPendingFinishAttach(app, true);
updateLruProcessLocked(app, false, null);
checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
@@ -4835,7 +4878,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
// Mark the finish attach application phase as completed
- app.setPendingFinishAttach(false);
+ mProcessStateController.setPendingFinishAttach(app, false);
final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
final String processName = app.processName;
@@ -4990,7 +5033,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// If another follow up update is needed, it will be scheduled by OomAdjuster.
mHandler.removeMessages(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG);
synchronized (this) {
- mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
}
}
@@ -5525,6 +5568,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (target instanceof PendingIntentRecord) {
final PendingIntentRecord originalRecord = (PendingIntentRecord) target;
+ addCreatorToken(intent, originalRecord.getPackageName());
+
// In multi-display scenarios, there can be background users who execute the
// PendingIntent. In these scenarios, we don't want to use the foreground user as the
// current user.
@@ -5563,19 +5608,23 @@ public class ActivityManagerService extends IActivityManager.Stub
intent = new Intent(Intent.ACTION_MAIN);
}
try {
+ final int callingUid = Binder.getCallingUid();
+ final String packageName;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ packageName = AppGlobals.getPackageManager().getNameForUid(callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
if (allowlistToken != null) {
- final int callingUid = Binder.getCallingUid();
- final String packageName;
- final long token = Binder.clearCallingIdentity();
- try {
- packageName = AppGlobals.getPackageManager().getNameForUid(callingUid);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target."
+ " Calling package: " + packageName + "; intent: " + intent
+ "; options: " + options);
}
+
+ addCreatorToken(intent, packageName);
+
target.send(code, intent, resolvedType, null, null,
requiredPermission, options);
} catch (RemoteException e) {
@@ -5789,10 +5838,10 @@ public class ActivityManagerService extends IActivityManager.Stub
if (pr == null) {
return;
}
- pr.mState.setForcingToImportant(null);
+ mProcessStateController.setForcingToImportant(pr, null);
clearProcessForegroundLocked(pr);
}
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -5815,7 +5864,7 @@ public class ActivityManagerService extends IActivityManager.Stub
oldToken.token.unlinkToDeath(oldToken, 0);
mImportantProcesses.remove(pid);
if (pr != null) {
- pr.mState.setForcingToImportant(null);
+ mProcessStateController.setForcingToImportant(pr, null);
}
changed = true;
}
@@ -5829,7 +5878,7 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
token.linkToDeath(newToken, 0);
mImportantProcesses.put(pid, newToken);
- pr.mState.setForcingToImportant(newToken);
+ mProcessStateController.setForcingToImportant(pr, newToken);
changed = true;
} catch (RemoteException e) {
// If the process died while doing this, we will later
@@ -5839,7 +5888,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (changed) {
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -7259,7 +7308,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
app.setPersistent(true);
- app.mState.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(app, ProcessList.PERSISTENT_PROC_ADJ);
}
if (app.getThread() == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
@@ -7362,7 +7411,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
- mOomAdjuster.onWakefulnessChanged(wakefulness);
+ mProcessStateController.setWakefulness(wakefulness);
updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
}
@@ -7631,7 +7680,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
public void requestBugReportWithDescription(@Nullable String shareTitle,
@Nullable String shareDescription, int bugreportType, long nonce,
- @Nullable Uri extraAttachment) {
+ @Nullable List<Uri> extraAttachments) {
String type = null;
switch (bugreportType) {
case BugreportParams.BUGREPORT_MODE_FULL:
@@ -7686,8 +7735,9 @@ public class ActivityManagerService extends IActivityManager.Stub
triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_NONCE, nonce);
- if (extraAttachment != null) {
- triggerShellBugreport.putExtra(EXTRA_EXTRA_ATTACHMENT_URI, extraAttachment);
+ if (extraAttachments != null && !extraAttachments.isEmpty()) {
+ triggerShellBugreport.putParcelableArrayListExtra(EXTRA_EXTRA_ATTACHMENT_URIS,
+ new ArrayList(extraAttachments));
triggerShellBugreport.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -7746,9 +7796,9 @@ public class ActivityManagerService extends IActivityManager.Stub
* Takes an interactive bugreport with a progress notification. Also attaches given file uri.
*/
@Override
- public void requestBugReportWithExtraAttachment(@NonNull Uri extraAttachment) {
+ public void requestBugReportWithExtraAttachments(@NonNull List<Uri> extraAttachments) {
requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_INTERACTIVE, 0L,
- extraAttachment);
+ extraAttachments);
}
/**
@@ -8259,7 +8309,16 @@ public class ActivityManagerService extends IActivityManager.Stub
setThreadScheduler(proc.getRenderThreadTid(),
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
- setThreadPriority(proc.getRenderThreadTid(), THREAD_PRIORITY_TOP_APP_BOOST);
+ if (Flags.resetOnForkEnabled()) {
+ if (Process.getThreadScheduler(proc.getRenderThreadTid())
+ == Process.SCHED_OTHER) {
+ Process.setThreadScheduler(proc.getRenderThreadTid(),
+ Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
+ 0);
+ }
+ }
+ setThreadPriority(proc.getRenderThreadTid(),
+ THREAD_PRIORITY_TOP_APP_BOOST);
}
}
} else {
@@ -8322,16 +8381,10 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "setHasTopUi called on unknown pid: " + pid);
return;
}
- if (pr.mState.hasTopUi() != hasTopUi) {
- if (DEBUG_OOM_ADJ) {
- Slog.d(TAG, "Setting hasTopUi=" + hasTopUi + " for pid=" + pid);
- }
- pr.mState.setHasTopUi(hasTopUi);
- changed = true;
- }
+ changed = mProcessStateController.setHasTopUi(pr, hasTopUi);
}
if (changed) {
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
} finally {
@@ -11291,7 +11344,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
pw.println(" mFgsStartTempAllowList:");
final long currentTimeNow = System.currentTimeMillis();
- final long elapsedRealtimeNow = SystemClock.elapsedRealtime();
+ final long tempAllowlistCurrentTime =
+ com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist()
+ ? SystemClock.uptimeMillis() : SystemClock.elapsedRealtime();
mFgsStartTempAllowList.forEach((uid, entry) -> {
pw.print(" " + UserHandle.formatUid(uid) + ": ");
entry.second.dump(pw);
@@ -11299,7 +11354,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Convert entry.mExpirationTime, which is an elapsed time since boot,
// to a time since epoch (i.e. System.currentTimeMillis()-based time.)
final long expirationInCurrentTime =
- currentTimeNow - elapsedRealtimeNow + entry.first;
+ currentTimeNow - tempAllowlistCurrentTime + entry.first;
TimeUtils.dumpTimeWithDelta(pw, expirationInCurrentTime, currentTimeNow);
pw.println();
});
@@ -13478,16 +13533,11 @@ public class ActivityManagerService extends IActivityManager.Stub
if (backupTarget != null && pid == backupTarget.app.getPid()) {
if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ backupTarget.appInfo + " died during backup");
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentDisconnectedForUser(app.userId, app.info.packageName);
- } catch (RemoteException e) {
- // can't happen; backup manager is local
- }
+ mHandler.post(() -> {
+ try {
+ getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName);
+ } catch (RemoteException e) {
+ // can't happen; backup manager is local
}
});
}
@@ -13646,6 +13696,7 @@ public class ActivityManagerService extends IActivityManager.Stub
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
+ addCreatorToken(service, callingPackage);
if (service != null) {
// Refuse possible leaked file descriptors
if (service.hasFileDescriptors()) {
@@ -13907,6 +13958,7 @@ public class ActivityManagerService extends IActivityManager.Stub
validateServiceInstanceName(instanceName);
+ addCreatorToken(service, callingPackage);
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
final ComponentName cn = service.getComponent();
@@ -13957,14 +14009,14 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- public void unbindFinished(IBinder token, Intent intent, boolean doRebind) {
+ public void unbindFinished(IBinder token, Intent intent) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
synchronized(this) {
- mServices.unbindFinishedLocked((ServiceRecord)token, intent, doRebind);
+ mServices.unbindFinishedLocked((ServiceRecord)token, intent);
}
}
@@ -13988,7 +14040,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// instantiated. The backup agent will invoke backupAgentCreated() on the
// activity manager to announce its creation.
public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
- @BackupDestination int backupDestination) {
+ @BackupDestination int backupDestination, boolean useRestrictedMode) {
long startTimeNs = SystemClock.uptimeNanos();
if (DEBUG_BACKUP) {
Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
@@ -14073,7 +14125,8 @@ public class ActivityManagerService extends IActivityManager.Stub
+ app.packageName + ": " + e);
}
- BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
+ BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination,
+ useRestrictedMode);
ComponentName hostingName =
(backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
|| backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE)
@@ -14099,15 +14152,20 @@ public class ActivityManagerService extends IActivityManager.Stub
// process, etc, then mark it as being in full backup so that certain calls to the
// process can be blocked. This is not reset to false anywhere because we kill the
// process after the full backup is done and the ProcessRecord will vaporize anyway.
- if (UserHandle.isApp(app.uid) &&
- backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
+ if (UserHandle.isApp(app.uid)
+ && backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && r.useRestrictedMode) {
proc.setInFullBackup(true);
}
r.app = proc;
+ // TODO(b/369300367): This code suggests there could be a previous backup being
+ // replaced here, but an OomAdjsuter update is not triggered on the previous app
+ // (whose state will change from being removed from mBackupTargets).
final BackupRecord backupTarget = mBackupTargets.get(targetUserId);
oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1;
newBackupUid = proc.isInFullBackup() ? r.appInfo.uid : -1;
mBackupTargets.put(targetUserId, r);
+ mProcessStateController.setBackupTarget(proc, targetUserId);
proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -14161,6 +14219,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mBackupTargets.removeAt(indexOfKey);
}
+ mProcessStateController.stopBackupTarget(userId);
}
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
@@ -14193,9 +14252,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final long oldIdent = Binder.clearCallingIdentity();
try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentConnectedForUser(userId, agentPackageName, agent);
+ getBackupManager().agentConnectedForUser(userId, agentPackageName, agent);
} catch (RemoteException e) {
// can't happen; the backup manager service is local
} catch (Exception e) {
@@ -14239,6 +14296,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = backupTarget.app;
+ // TODO(b/369300367): Triggering the update before the state is actually set
+ // seems wrong.
updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
proc.setInFullBackup(false);
proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -14257,6 +14316,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
} finally {
mBackupTargets.delete(userId);
+ mProcessStateController.stopBackupTarget(userId);
}
}
@@ -14292,6 +14352,10 @@ public class ActivityManagerService extends IActivityManager.Stub
mBroadcastController.unregisterReceiver(receiver);
}
+ public List<IntentFilter> getRegisteredIntentFilters(IIntentReceiver receiver) {
+ return mBroadcastController.getRegisteredIntentFilters(receiver);
+ }
+
@GuardedBy("this")
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
@@ -15329,7 +15393,8 @@ public class ActivityManagerService extends IActivityManager.Stub
proc.info.packageName, proc.info.uid, proc.getPid(), isForeground);
}
}
- psr.setHasForegroundServices(isForeground, fgServiceTypes, hasTypeNoneFgs);
+ mProcessStateController.setHasForegroundServices(psr, isForeground, fgServiceTypes,
+ hasTypeNoneFgs);
ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
proc.info.uid);
if (isForeground) {
@@ -15360,7 +15425,7 @@ public class ActivityManagerService extends IActivityManager.Stub
ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes);
}
if (oomAdj) {
- updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(proc, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -15410,7 +15475,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@GuardedBy("this")
void enqueueOomAdjTargetLocked(ProcessRecord app) {
- mOomAdjuster.enqueueOomAdjTargetLocked(app);
+ mProcessStateController.enqueueUpdateTarget(app);
}
/**
@@ -15418,7 +15483,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@GuardedBy("this")
void removeOomAdjTargetLocked(ProcessRecord app, boolean procDied) {
- mOomAdjuster.removeOomAdjTargetLocked(app, procDied);
+ mProcessStateController.removeUpdateTarget(app, procDied);
}
/**
@@ -15427,7 +15492,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@GuardedBy("this")
void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
- mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
+ mProcessStateController.runPendingUpdate(oomAdjReason);
}
static final class ProcStatsRunnable implements Runnable {
@@ -15446,7 +15511,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
- mOomAdjuster.updateOomAdjLocked(oomAdjReason);
+ mProcessStateController.runFullUpdate(oomAdjReason);
}
/**
@@ -15458,7 +15523,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@GuardedBy("this")
final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
- return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
+ return mProcessStateController.runUpdate(app, oomAdjReason);
}
@Override
@@ -15723,7 +15788,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy({"this", "mProcLock"})
final void setUidTempAllowlistStateLSP(int uid, boolean onAllowlist) {
- mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
+ mProcessStateController.setUidTempAllowlistStateLSP(uid, onAllowlist);
}
private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
@@ -16774,12 +16839,9 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
}
- if (pr.mState.hasOverlayUi() == hasOverlayUi) {
- return;
+ if (mProcessStateController.setHasOverlayUi(pr, hasOverlayUi)) {
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
- pr.mState.setHasOverlayUi(hasOverlayUi);
- //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -17185,6 +17247,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.v(TAG_SERVICE,
"startServiceInPackage: " + service + " type=" + resolvedType);
}
+ addCreatorToken(service, callingPackage);
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
@@ -17902,6 +17965,24 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public void notifyActiveMediaForegroundService(@NonNull String packageName,
+ @UserIdInt int userId, int notificationId) {
+ synchronized (ActivityManagerService.this) {
+ mServices.notifyActiveMediaForegroundServiceLocked(packageName, userId,
+ notificationId);
+ }
+ }
+
+ @Override
+ public void notifyInactiveMediaForegroundService(@NonNull String packageName,
+ @UserIdInt int userId, int notificationId) {
+ synchronized (ActivityManagerService.this) {
+ mServices.notifyInactiveMediaForegroundServiceLocked(packageName, userId,
+ notificationId);
+ }
+ }
+
+ @Override
public ArraySet<String> getClientPackages(String servicePackageName) {
synchronized (ActivityManagerService.this) {
return mServices.getClientPackagesLocked(servicePackageName);
@@ -17982,14 +18063,6 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
int userId) {
- // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
- if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
- throw new IllegalArgumentException("Unsupported userId");
- }
-
- mUserController.handleIncomingUser(pid, uid, userId, true,
- ALLOW_NON_FULL, "addStartInfoTimestampSystem", null);
-
addStartInfoTimestampInternal(key, timestampNs, userId, uid);
}
@@ -18012,6 +18085,31 @@ public class ActivityManagerService extends IActivityManager.Stub
userId, reason, exitInfoReason);
}
}
+
+ @Override
+ public void getExecutableMethodFileOffsets(@NonNull String processName,
+ int pid, int uid, @NonNull MethodDescriptor methodDescriptor,
+ @NonNull IOffsetCallback callback) {
+ final IApplicationThread thread;
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord record = mProcessList.getProcessRecordLocked(processName, uid);
+ if (record == null || record.getPid() != pid) {
+ throw new NoSuchElementException();
+ }
+ thread = record.getThread();
+ }
+ try {
+ thread.getExecutableMethodFileOffsets(methodDescriptor, callback);
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "IApplicationThread.getExecutableMethodFileOffsets failed", e);
+ }
+ }
+
+ @Override
+ public void addCreatorToken(Intent intent, String creatorPackage) {
+ ActivityManagerService.this.addCreatorToken(intent, creatorPackage);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18437,25 +18535,34 @@ public class ActivityManagerService extends IActivityManager.Stub
"Cannot kill the dependents of a package without its name.");
}
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, ALLOW_FULL_ONLY, "killPackageDependents", null);
+ final int[] userIds = mUserController.expandUserId(userId);
+
final long callingId = Binder.clearCallingIdentity();
IPackageManager pm = AppGlobals.getPackageManager();
- int pkgUid = -1;
- try {
- pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
- } catch (RemoteException e) {
- }
- if (userId != UserHandle.USER_ALL && pkgUid == -1) {
- throw new IllegalArgumentException(
- "Cannot kill dependents of non-existing package " + packageName);
- }
try {
- synchronized(this) {
- synchronized (mProcLock) {
- mProcessList.killPackageProcessesLSP(packageName, UserHandle.getAppId(pkgUid),
- userId, ProcessList.FOREGROUND_APP_ADJ,
- ApplicationExitInfo.REASON_DEPENDENCY_DIED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- "dep: " + packageName);
+ for (int targetUserId : userIds) {
+ int pkgUid = -1;
+ try {
+ pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,
+ targetUserId);
+ } catch (RemoteException e) {
+ }
+ if (userId != UserHandle.USER_ALL && pkgUid == -1) {
+ throw new IllegalArgumentException(
+ "Cannot kill dependents of non-existing package " + packageName);
+ }
+ synchronized (this) {
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(packageName,
+ UserHandle.getAppId(pkgUid),
+ targetUserId,
+ ProcessList.FOREGROUND_APP_ADJ,
+ ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "dep: " + packageName);
+ }
}
}
} finally {
@@ -19043,8 +19150,13 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@Override
public boolean enableFgsNotificationRateLimit(boolean enable) {
- enforceCallingPermission(permission.WRITE_DEVICE_CONFIG,
- "enableFgsNotificationRateLimit");
+ if (android.security.Flags.protectDeviceConfigFlags()) {
+ enforceCallingHasAtLeastOnePermission("enableFgsNotificationRateLimit",
+ permission.WRITE_DEVICE_CONFIG, permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
+ } else {
+ enforceCallingPermission(permission.WRITE_DEVICE_CONFIG,
+ "enableFgsNotificationRateLimit");
+ }
synchronized (this) {
return mServices.enableFgsNotificationRateLimitLocked(enable);
}
@@ -19139,4 +19251,233 @@ public class ActivityManagerService extends IActivityManager.Stub
Freezer getFreezer() {
return mFreezer;
}
+
+ // Set of IntentCreatorToken objects that are currently active.
+ private static final Map<IntentCreatorToken.Key, WeakReference<IntentCreatorToken>>
+ sIntentCreatorTokenCache = new ConcurrentHashMap<>();
+
+ private static Handler sCreatorTokenCacheCleaner;
+ /**
+ * A binder token used to keep track of which app created the intent. This token can be used to
+ * defend against intent redirect attacks. It stores uid of the intent creator and key fields of
+ * the intent to make it impossible for attacker to fake uid with a malicious intent.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static final class IntentCreatorToken extends Binder {
+ @NonNull
+ private final Key mKeyFields;
+ private final WeakReference<IntentCreatorToken> mRef;
+
+ public IntentCreatorToken(int creatorUid, String creatorPackage, Intent intent) {
+ super();
+ this.mKeyFields = new Key(creatorUid, creatorPackage, intent);
+ mRef = new WeakReference<>(this);
+ }
+
+ public int getCreatorUid() {
+ return mKeyFields.mCreatorUid;
+ }
+
+ public String getCreatorPackage() {
+ return mKeyFields.mCreatorPackage;
+ }
+
+ @VisibleForTesting
+ public @NonNull Key getKeyFields() {
+ return mKeyFields;
+ }
+
+ public static boolean isValid(@NonNull Intent intent) {
+ IBinder binder = intent.getCreatorToken();
+ IntentCreatorToken token = null;
+ if (binder instanceof IntentCreatorToken) {
+ token = (IntentCreatorToken) binder;
+ }
+ return token != null && token.mKeyFields.equals(
+ new Key(token.mKeyFields.mCreatorUid, token.mKeyFields.mCreatorPackage,
+ intent));
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ sCreatorTokenCacheCleaner.sendMessage(PooledLambda.obtainMessage(
+ IntentCreatorToken::completeFinalize, this));
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void completeFinalize() {
+ synchronized (sIntentCreatorTokenCache) {
+ WeakReference<IntentCreatorToken> current = sIntentCreatorTokenCache.get(
+ mKeyFields);
+ if (current == mRef) {
+ sIntentCreatorTokenCache.remove(mKeyFields);
+ }
+ }
+ }
+
+ private static class Key {
+ private Key(int creatorUid, String creatorPackage, Intent intent) {
+ this.mCreatorUid = creatorUid;
+ this.mCreatorPackage = creatorPackage;
+ this.mAction = intent.getAction();
+ this.mData = intent.getData();
+ this.mType = intent.getType();
+ this.mPackage = intent.getPackage();
+ this.mComponent = intent.getComponent();
+ this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
+ ClipData clipData = intent.getClipData();
+ if (clipData != null) {
+ clipData = clipData.cloneOnlyUriItems();
+ if (clipData != null) {
+ List<Uri> clipDataUris = new ArrayList<>();
+ clipData.collectUris(clipDataUris);
+ if (!clipDataUris.isEmpty()) {
+ this.mClipDataUris = clipDataUris;
+ }
+ }
+ }
+ }
+
+ private final int mCreatorUid;
+ private final String mCreatorPackage;
+ private final String mAction;
+ private final Uri mData;
+ private final String mType;
+ private final String mPackage;
+ private final ComponentName mComponent;
+ private final int mFlags;
+ private List<Uri> mClipDataUris;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Key key = (Key) o;
+ return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags
+ && Objects.equals(mCreatorPackage, key.mCreatorPackage)
+ && Objects.equals(mAction, key.mAction)
+ && Objects.equals(mData, key.mData)
+ && Objects.equals(mType, key.mType)
+ && Objects.equals(mPackage, key.mPackage)
+ && Objects.equals(mComponent, key.mComponent)
+ && Objects.equals(mClipDataUris, key.mClipDataUris);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCreatorUid, mCreatorPackage, mAction, mData, mType, mPackage,
+ mComponent, mFlags, mClipDataUris);
+ }
+ }
+ }
+
+ /**
+ * Add a creator token for all embedded intents (stored as extra) of the given intent.
+ *
+ * @param intent The given intent
+ * @hide
+ */
+ public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
+ if (!preventIntentRedirect()) return;
+ if (intent == null) return;
+
+ if (((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0)
+ && intent.getExtras() != null && intent.getExtras().hasIntent()) {
+ Slog.wtf(TAG,
+ "[IntentRedirect] The intent does not have its nested keys collected as a "
+ + "preparation for creating intent creator tokens. Intent: "
+ + intent + "; creatorPackage: " + creatorPackage);
+ if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(mContext,
+ "Nested keys not collected. go/report-bug-intentRedir to report a"
+ + " bug", Toast.LENGTH_LONG).show());
+ }
+ if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
+ // this flag will be internal only, not ramped to public.
+ throw new SecurityException(
+ "The intent does not have its nested keys collected as a preparation for "
+ + "creating intent creator tokens. Intent: "
+ + intent + "; creatorPackage: " + creatorPackage);
+ }
+ if (preventIntentRedirectCollectNestedKeysOnServerIfNotCollected()) {
+ // this flag will be ramped to public.
+ intent.collectExtraIntentKeys();
+ }
+ }
+
+ String targetPackage = intent.getComponent() != null
+ ? intent.getComponent().getPackageName()
+ : intent.getPackage();
+ final boolean isCreatorSameAsTarget = creatorPackage != null && creatorPackage.equals(
+ targetPackage);
+ final boolean noExtraIntentKeys =
+ intent.getExtraIntentKeys() == null || intent.getExtraIntentKeys().isEmpty();
+ final int creatorUid = noExtraIntentKeys ? DEFAULT_INTENT_CREATOR_UID : Binder.getCallingUid();
+
+ intent.forEachNestedCreatorToken(extraIntent -> {
+ if (isCreatorSameAsTarget) {
+ FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, true);
+ return;
+ }
+ IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorUid,
+ creatorPackage);
+ if (creatorToken != null) {
+ extraIntent.setCreatorToken(creatorToken);
+ // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
+ Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
+ + creatorPackage + "; intent: " + extraIntent);
+ FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, false);
+ }
+ });
+ }
+
+ private IntentCreatorToken createIntentCreatorToken(Intent intent, int creatorUid,
+ String creatorPackage) {
+ if (IntentCreatorToken.isValid(intent)) return null;
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
+ return createOrGetIntentCreatorToken(intent, key);
+ }
+
+ /**
+ * @hide
+ */
+ @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL")
+ public IBinder refreshIntentCreatorToken(Intent intent) {
+ refreshIntentCreatorToken_enforcePermission();
+ IBinder binder = intent.getCreatorToken();
+ if (binder instanceof IntentCreatorToken) {
+ IntentCreatorToken token = (IntentCreatorToken) binder;
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(token.getCreatorUid(),
+ token.getCreatorPackage(), intent);
+ return createOrGetIntentCreatorToken(intent, key);
+
+ } else {
+ return null;
+ }
+ }
+
+ private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent,
+ IntentCreatorToken.Key key) {
+ IntentCreatorToken token;
+ synchronized (sIntentCreatorTokenCache) {
+ WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
+ if (ref == null || ref.get() == null) {
+ token = new IntentCreatorToken(key.mCreatorUid, key.mCreatorPackage, intent);
+ sIntentCreatorTokenCache.put(key, token.mRef);
+ } else {
+ token = ref.get();
+ }
+ }
+ return token;
+ }
+
+ private IBackupManager getBackupManager() {
+ return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 93dfa0f9906d..9a63546bf5a7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -39,6 +39,8 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRI
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+import static com.android.media.flags.Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange;
+import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -445,6 +447,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runCapabilities(pw);
case "set-app-zygote-preload-timeout":
return runSetAppZygotePreloadTimeout(pw);
+ case "set-media-foreground-service":
+ return runSetMediaForegroundService(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -454,6 +458,53 @@ final class ActivityManagerShellCommand extends ShellCommand {
return -1;
}
+ int runSetMediaForegroundService(PrintWriter pw) throws RemoteException {
+ mInternal.enforceCallingPermission(
+ android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
+ "runSetMediaForegroundService()");
+ final PrintWriter err = getErrPrintWriter();
+ if (!enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ err.println("Error: flag "
+ + FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE
+ + " not enabled");
+ return -1;
+ }
+ int userId = UserHandle.USER_CURRENT;
+ final String cmd = getNextArgRequired();
+ if ("inactive".equals(cmd) || "active".equals(cmd)) {
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId == UserHandle.USER_ALL) {
+ err.println(
+ "Error: Can't set media fgs with user 'all'");
+ return -1;
+ }
+ } else {
+ err.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ final String pkgName = getNextArgRequired();
+ final int notificationId = Integer.parseInt(getNextArgRequired());
+ if (notificationId == 0) {
+ err.println("Error: notification id cannot be zero");
+ return -1;
+ }
+ if ("inactive".equals(cmd)) {
+ mInternal.mInternal.notifyInactiveMediaForegroundService(pkgName,
+ userId, notificationId);
+ } else {
+ mInternal.mInternal.notifyActiveMediaForegroundService(pkgName,
+ userId, notificationId);
+ }
+ return 0;
+ }
+ err.println("Error: Unknown set-media-foreground-service command: " + cmd);
+ return -1;
+ }
+
int runSetAppZygotePreloadTimeout(PrintWriter pw) throws RemoteException {
final String timeout = getNextArgRequired();
final int timeoutMs = Integer.parseInt(timeout);
@@ -806,6 +857,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
options.setDismissKeyguardIfInsecure();
}
+ intent.collectExtraIntentKeys();
if (mWaitOption) {
result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
mimeType, null, null, 0, mStartFlags, profilerInfo,
@@ -924,6 +976,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
pw.println("Starting service: " + intent);
pw.flush();
+ intent.collectExtraIntentKeys();
ComponentName cn = mInterface.startService(null, intent, intent.getType(),
asForeground, SHELL_PACKAGE_NAME, null, mUserId);
if (cn == null) {
@@ -956,6 +1009,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
pw.println("Stopping service: " + intent);
pw.flush();
+ intent.collectExtraIntentKeys();
int result = mInterface.stopService(null, intent, intent.getType(), mUserId);
if (result == 0) {
err.println("Service not stopped: was not running.");
@@ -4660,6 +4714,9 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" --protobuf: format output using protobuffer");
pw.println(" set-app-zygote-preload-timeout <TIMEOUT_IN_MS>");
pw.println(" Set the timeout for preloading code in the app-zygote");
+ pw.println(" set-media-foreground-service inactive|active [--user USER_ID] <PACKAGE>"
+ + " <NOTIFICATION_ID>");
+ pw.println(" Set an app's media service inactive or active.");
Intent.printIntentArgsHelp(pw, "");
}
}
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 9fc0bf920969..6d594ac38187 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -20,7 +20,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.content.pm.ApplicationInfo;
-import android.os.Process;
+import android.os.ProfilingServiceHelper;
+import android.os.ProfilingTrigger;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
@@ -240,6 +241,15 @@ class AnrHelper {
|| startTime < SELF_ONLY_AFTER_BOOT_MS;
r.appNotResponding(onlyDumpSelf);
final long endTime = SystemClock.uptimeMillis();
+
+ if (android.os.profiling.Flags.systemTriggeredProfilingNew() && r.mAppInfo != null
+ && r.mAppInfo.packageName != null) {
+ ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(
+ r.mUid,
+ r.mAppInfo.packageName,
+ ProfilingTrigger.TRIGGER_TYPE_ANR);
+ }
+
Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
+ (endTime - startTime) + "ms, latency " + reportLatency
+ (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms"));
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 415f78aa3ee5..b7a5f3e4099a 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -598,7 +598,7 @@ class AppErrors {
}
if (r != null) {
- mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
+ mPackageWatchdog.notifyPackageFailure(r.getPackageListWithVersionCode(),
PackageWatchdog.FAILURE_REASON_APP_CRASH);
synchronized (mService) {
@@ -1142,7 +1142,7 @@ class AppErrors {
}
// Notify PackageWatchdog without the lock held
if (packageList != null) {
- mPackageWatchdog.onPackageFailure(packageList,
+ mPackageWatchdog.notifyPackageFailure(packageList,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
}
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 79a0d737bf6c..6b24df4a1fa8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1356,6 +1356,7 @@ public class AppProfiler {
@GuardedBy("mService")
void setMemFactorOverrideLocked(@MemFactor int factor) {
mMemFactorOverride = factor;
+ mService.mProcessStateController.setIsLastMemoryLevelNormal(isLastMemoryLevelNormal());
}
@GuardedBy({"mService", "mProcLock"})
@@ -1423,6 +1424,7 @@ public class AppProfiler {
}
mLastMemoryLevel = memFactor;
+ mService.mProcessStateController.setIsLastMemoryLevelNormal(isLastMemoryLevelNormal());
mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
// Dispatch UI_HIDDEN to processes that need it
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 6aadcdc74870..961022b7231b 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ApplicationStartInfo;
import android.app.Flags;
import android.app.IApplicationStartInfoCompleteListener;
@@ -280,7 +281,11 @@ public final class AppStartInfoTracker {
mTemporaryInProgressIndexes.clear();
}
- void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
+ /**
+ * Should only be called for Activity launch sequences from an instance of
+ * {@link ActivityMetricsLaunchObserver}.
+ */
+ void onActivityIntentStarted(@NonNull Intent intent, long timestampNanos) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -291,6 +296,10 @@ public final class AppStartInfoTracker {
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+ if (android.app.Flags.appStartInfoComponent()) {
+ start.setStartComponent(ApplicationStartInfo.START_COMPONENT_ACTIVITY);
+ }
+
// TODO: handle possible alarm activity start.
if (intent != null && intent.getCategories() != null
&& intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
@@ -303,7 +312,11 @@ public final class AppStartInfoTracker {
}
}
- void onIntentFailed(long id) {
+ /**
+ * Should only be called for Activity launch sequences from an instance of
+ * {@link ActivityMetricsLaunchObserver}.
+ */
+ void onActivityIntentFailed(long id) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -322,6 +335,10 @@ public final class AppStartInfoTracker {
}
}
+ /**
+ * Should only be called for Activity launch sequences from an instance of
+ * {@link ActivityMetricsLaunchObserver}.
+ */
void onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app) {
synchronized (mLock) {
if (!mEnabled) {
@@ -349,6 +366,10 @@ public final class AppStartInfoTracker {
}
}
+ /**
+ * Should only be called for Activity launch sequences from an instance of
+ * {@link ActivityMetricsLaunchObserver}.
+ */
void onActivityLaunchCancelled(long id) {
synchronized (mLock) {
if (!mEnabled) {
@@ -368,6 +389,10 @@ public final class AppStartInfoTracker {
}
}
+ /**
+ * Should only be called for Activity launch sequences from an instance of
+ * {@link ActivityMetricsLaunchObserver}.
+ */
void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
int launchMode) {
synchronized (mLock) {
@@ -391,23 +416,29 @@ public final class AppStartInfoTracker {
}
}
- void onReportFullyDrawn(long id, long timestampNanos) {
+ /**
+ * Should only be called for Activity launch sequences from an instance of
+ * {@link ActivityMetricsLaunchObserver}.
+ */
+ @Nullable
+ ApplicationStartInfo onActivityReportFullyDrawn(long id, long timestampNanos) {
synchronized (mLock) {
if (!mEnabled) {
- return;
+ return null;
}
int index = mInProgressRecords.indexOfKey(id);
if (index < 0) {
- return;
+ return null;
}
ApplicationStartInfo info = mInProgressRecords.valueAt(index);
if (info == null) {
mInProgressRecords.removeAt(index);
- return;
+ return null;
}
info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
timestampNanos);
mInProgressRecords.removeAt(index);
+ return info;
}
}
@@ -424,6 +455,10 @@ public final class AppStartInfoTracker {
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+ if (android.app.Flags.appStartInfoComponent()) {
+ start.setStartComponent(ApplicationStartInfo.START_COMPONENT_SERVICE);
+ }
+
// TODO: handle possible alarm service start.
start.setReason(serviceRecord.permission != null
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
@@ -455,6 +490,11 @@ public final class AppStartInfoTracker {
start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
}
start.setIntent(intent);
+
+ if (android.app.Flags.appStartInfoComponent()) {
+ start.setStartComponent(ApplicationStartInfo.START_COMPONENT_BROADCAST);
+ }
+
addStartInfoLocked(start);
}
}
@@ -472,6 +512,11 @@ public final class AppStartInfoTracker {
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
+
+ if (android.app.Flags.appStartInfoComponent()) {
+ start.setStartComponent(ApplicationStartInfo.START_COMPONENT_CONTENT_PROVIDER);
+ }
+
addStartInfoLocked(start);
}
}
@@ -490,6 +535,11 @@ public final class AppStartInfoTracker {
start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
: ApplicationStartInfo.START_TYPE_WARM);
start.setReason(ApplicationStartInfo.START_REASON_BACKUP);
+
+ if (android.app.Flags.appStartInfoComponent()) {
+ start.setStartComponent(ApplicationStartInfo.START_COMPONENT_OTHER);
+ }
+
addStartInfoLocked(start);
}
}
@@ -584,12 +634,20 @@ public final class AppStartInfoTracker {
}
final ApplicationStartInfo info = new ApplicationStartInfo(raw);
+ int uid = raw.getRealUid();
+
+ // Isolated process starts won't be reasonably accessible if stored by their uid, don't
+ // store them.
+ if (com.android.server.am.Flags.appStartInfoIsolatedProcess()
+ && UserHandle.isIsolated(uid)) {
+ return null;
+ }
- AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid());
+ AppStartInfoContainer container = mData.get(raw.getPackageName(), uid);
if (container == null) {
container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
- container.mUid = info.getRealUid();
- mData.put(raw.getPackageName(), raw.getRealUid(), container);
+ container.mUid = uid;
+ mData.put(raw.getPackageName(), uid, container);
}
container.addStartInfoLocked(info);
@@ -958,7 +1016,19 @@ public final class AppStartInfoTracker {
case (int) AppsStartInfoProto.Package.USERS:
AppStartInfoContainer container =
new AppStartInfoContainer(mAppStartInfoHistoryListSize);
- int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS);
+ int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS,
+ pkgName);
+
+ // If the isolated process flag is enabled and the uid is that of an isolated
+ // process, then break early so that the container will not be added to mData.
+ // This is expected only as a one time mitigation, records added after this flag
+ // is enabled should always return false for isIsolated and thereby always
+ // continue on.
+ if (com.android.server.am.Flags.appStartInfoIsolatedProcess()
+ && UserHandle.isIsolated(uid)) {
+ break;
+ }
+
synchronized (mLock) {
mData.put(pkgName, uid, container);
}
@@ -1356,7 +1426,7 @@ public final class AppStartInfoTracker {
proto.end(token);
}
- int readFromProto(ProtoInputStream proto, long fieldId)
+ int readFromProto(ProtoInputStream proto, long fieldId, String packageName)
throws IOException, WireTypeMismatchException, ClassNotFoundException {
long token = proto.start(fieldId);
for (int next = proto.nextField();
@@ -1371,6 +1441,7 @@ public final class AppStartInfoTracker {
// have a create time.
ApplicationStartInfo info = new ApplicationStartInfo(0);
info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+ info.setPackageName(packageName);
mInfos.add(info);
break;
case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED:
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index 0b056d7883bf..64cc6f0e66e3 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -32,15 +32,18 @@ final class BackupRecord {
final int userId; // user for which backup is performed
final int backupMode; // full backup / incremental / restore
@BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
+ final boolean useRestrictedMode; // whether the app should be put into restricted backup mode
ProcessRecord app; // where this agent is running or null
// ----- Implementation -----
- BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
+ BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination,
+ boolean _useRestrictedMode) {
appInfo = _appInfo;
backupMode = _backupMode;
userId = _userId;
backupDestination = _backupDestination;
+ useRestrictedMode = _useRestrictedMode;
}
public String toString() {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 75e9fadbd917..c27126a01a32 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -25,6 +25,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.os.BatteryConsumer.POWER_COMPONENT_BASE;
import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
@@ -61,6 +62,7 @@ import android.os.BatteryUsageStatsQuery;
import android.os.Binder;
import android.os.BluetoothBatteryStats;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -122,6 +124,7 @@ import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
@@ -193,8 +196,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
private final BatteryStats.BatteryStatsDumpHelper mDumpHelper;
private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver();
private final PowerAttributor mPowerAttributor;
+ private final PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
private volatile boolean mMonitorEnabled = true;
+ private boolean mRailsStatsCollectionEnabled = true;
private native void getRailEnergyPowerStats(RailStats railStats);
private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8
@@ -211,6 +216,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Object mLock = new Object();
+ private final ConditionVariable mSystemReady = new ConditionVariable(false);
private final Object mPowerStatsLock = new Object();
@GuardedBy("mPowerStatsLock")
@@ -311,8 +317,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ public void setRailsStatsCollectionEnabled(boolean railsStatsCollectionEnabled) {
+ mRailsStatsCollectionEnabled = railsStatsCollectionEnabled;
+ }
+
@Override
public void fillRailDataStats(RailStats railStats) {
+ if (!mRailsStatsCollectionEnabled) {
+ railStats.setRailStatsAvailability(false);
+ return;
+ }
+
if (DBG) Slog.d(TAG, "begin getRailEnergyPowerStats");
try {
getRailEnergyPowerStats(railStats);
@@ -402,6 +417,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mHandlerThread = new HandlerThread("batterystats-handler");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mHandler.post(mSystemReady::block);
mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));
mPowerProfile = new PowerProfile(context);
@@ -422,7 +438,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies, mPowerStatsUidResolver);
- mWorker = new BatteryExternalStatsWorker(context, mStats);
+ mWorker = new BatteryExternalStatsWorker(context, mStats, mHandler);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
@@ -432,12 +448,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mPowerStatsStore = new PowerStatsStore(systemDir, mHandler);
mPowerAttributor = new MultiStatePowerAttributor(mContext, mPowerStatsStore, mPowerProfile,
- mCpuScalingPolicies, mPowerStatsUidResolver);
+ mCpuScalingPolicies, () -> mStats.getBatteryCapacity());
mPowerStatsScheduler = createPowerStatsScheduler(mContext);
+
+ int accumulatedBatteryUsageStatsSpanSize = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_accumulatedBatteryUsageStatsSpanSize);
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
mPowerAttributor, mPowerProfile, mCpuScalingPolicies,
- mPowerStatsStore, Clock.SYSTEM_CLOCK);
- mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore);
+ mPowerStatsStore, accumulatedBatteryUsageStatsSpanSize, Clock.SYSTEM_CLOCK,
+ mMonotonicClock);
mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider);
mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
@@ -504,6 +523,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
public void systemServicesReady() {
+ mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore,
+ isBatteryUsageStatsAccumulationSupported());
+
MultiStatePowerAttributor attributor = (MultiStatePowerAttributor) mPowerAttributor;
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CPU,
Flags.streamlinedBatteryStats());
@@ -511,6 +533,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
BatteryConsumer.POWER_COMPONENT_CPU,
Flags.streamlinedBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+ Flags.streamlinedMiscBatteryStats());
+ attributor.setPowerComponentSupported(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+ Flags.streamlinedMiscBatteryStats());
+
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_SCREEN,
Flags.streamlinedMiscBatteryStats());
attributor.setPowerComponentSupported(
@@ -578,6 +606,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
BatteryConsumer.POWER_COMPONENT_CAMERA,
Flags.streamlinedMiscBatteryStats());
+ // Currently unimplemented.
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MEMORY,
+ Flags.streamlinedMiscBatteryStats());
+ attributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_MEMORY,
+ Flags.streamlinedMiscBatteryStats());
+
// By convention POWER_COMPONENT_ANY represents custom Energy Consumers
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_ANY,
Flags.streamlinedMiscBatteryStats());
@@ -585,6 +619,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
BatteryConsumer.POWER_COMPONENT_ANY,
Flags.streamlinedMiscBatteryStats());
+ mStats.setMoveWscLoggingToNotifierEnabled(
+ mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled());
+
mWorker.systemServicesReady();
mStats.systemServicesReady(mContext);
mCpuWakeupStats.systemServicesReady();
@@ -619,6 +656,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
dataConnectionStats.startMonitoring();
registerStatsCallbacks();
+ mSystemReady.open();
+ }
+
+ private static boolean isBatteryUsageStatsAccumulationSupported() {
+ return Flags.accumulateBatteryUsageStats()
+ && Flags.streamlinedBatteryStats()
+ && Flags.streamlinedConnectivityBatteryStats()
+ && Flags.streamlinedMiscBatteryStats();
}
/**
@@ -662,6 +707,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if (nc.hasTransport(TRANSPORT_CELLULAR)) {
return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
}
+ // For TRANSPORT_BLUETOOTH, we have a separate channel to catch Bluetooth wakeups.
+ // See noteCpuWakingSysproxyPacket method.
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -684,6 +731,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
@Override
+ public void noteCpuWakingBluetoothProxyPacket(int uid, long elapsedMillis) {
+ if (uid < 0) {
+ Slog.e(TAG, "Invalid uid for waking bluetooth proxy packet: " + uid);
+ return;
+ }
+ noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, elapsedMillis, uid);
+ }
+
+ @Override
public void noteBinderCallStats(int workSourceUid, long incrementatCallCount,
Collection<BinderCallsStats.CallStat> callStats) {
synchronized (BatteryStatsService.this.mLock) {
@@ -709,6 +765,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
public void noteWakingAlarmBatch(long elapsedMillis, int... uids) {
noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, elapsedMillis, uids);
}
+
+ @Override
+ public int getOwnerUid(int uid) {
+ if (Process.isSdkSandboxUid(uid)) {
+ return Process.getAppUidForSdkSandboxUid(uid);
+ }
+ return mPowerStatsUidResolver.mapUid(uid);
+ }
}
/**
@@ -755,7 +819,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
private void syncStats(String reason, int flags) {
mStats.collectPowerStatsSamples();
- awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+ mWorker.scheduleSync(reason, flags);
+ awaitCompletion();
}
private void awaitCompletion() {
@@ -812,6 +877,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return sService;
}
+ /**
+ * Override the {@link IBatteryStats} service, for testing.
+ */
+ @VisibleForTesting
+ public static void overrideService(IBatteryStats service) {
+ sService = service;
+ }
+
@Override
public int getServiceType() {
return ServiceType.BATTERY_STATS;
@@ -1053,20 +1126,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
break;
}
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
- if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
- return StatsManager.PULL_SKIP;
- }
+ return StatsManager.PULL_SKIP;
- final BatteryUsageStatsQuery queryPowerProfile =
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includeProcessStateData()
- .includeVirtualUids()
- .powerProfileModeledOnly()
- .includePowerModels()
- .build();
- bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
- break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
return StatsManager.PULL_SKIP;
@@ -1100,14 +1161,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub
DEVICE_CONFIG_NAMESPACE,
MIN_CONSUMED_POWER_THRESHOLD_KEY,
0);
- final BatteryUsageStatsQuery query =
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includeProcessStateData()
- .includeVirtualUids()
- .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
- .build();
- bus = getBatteryUsageStats(List.of(query)).get(0);
+ BatteryUsageStatsQuery.Builder query = new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includeVirtualUids()
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold);
+
+ if (isBatteryUsageStatsAccumulationSupported()) {
+ query.accumulated();
+ }
+
+ bus = getBatteryUsageStats(List.of(query.build())).get(0);
final int pullResult =
new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
try {
@@ -1186,6 +1250,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mFrameworkStatsLogger = frameworkStatsLogger;
}
+ private static float clampPowerMah(double powerMah, String consumer) {
+ float resultPowerMah = Double.valueOf(powerMah).floatValue();
+ if (Float.isInfinite(resultPowerMah)) {
+ resultPowerMah = 0;
+ Slog.d(TAG, consumer + " reported powerMah float overflow : " + powerMah);
+ }
+ return resultPowerMah;
+ }
+
/**
* Generates StatsEvents for the supplied battery usage stats and adds them to
* the supplied list.
@@ -1206,10 +1279,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
bus.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
- final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+ final float totalDeviceConsumedPowerMah =
+ clampPowerMah(deviceConsumer.getConsumedPower(), "AggregateBatteryConsumer");
- for (@BatteryConsumer.PowerComponentId int componentIndex :
+ for (@BatteryConsumer.PowerComponentId int powerComponentId :
deviceConsumer.getPowerComponentIds()) {
+ if (powerComponentId == POWER_COMPONENT_BASE) {
+ continue;
+ }
for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
@@ -1221,7 +1298,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
totalDeviceConsumedPowerMah,
0,
deviceConsumer,
- componentIndex)) {
+ powerComponentId)) {
return StatsManager.PULL_SUCCESS;
}
}
@@ -1235,10 +1312,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
// Log single atom for BatteryUsageStats per uid/process_state/component/etc.
for (UidBatteryConsumer uidConsumer : uidConsumers) {
final int uid = uidConsumer.getUid();
- final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
- for (@BatteryConsumer.PowerComponentId int componentIndex :
+ final float totalConsumedPowerMah =
+ clampPowerMah(uidConsumer.getConsumedPower(), "uidConsumer-" + uid);
+
+ for (@BatteryConsumer.PowerComponentId int powerComponentId :
uidConsumer.getPowerComponentIds()) {
+ if (powerComponentId == POWER_COMPONENT_BASE) {
+ continue;
+ }
for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
@@ -1252,7 +1334,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
totalConsumedPowerMah,
timeInProcessStateMs,
uidConsumer,
- componentIndex)) {
+ powerComponentId)) {
return StatsManager.PULL_SUCCESS;
}
}
@@ -1276,9 +1358,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
final String powerComponentName = batteryConsumer.getPowerComponentName(componentId);
- final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+ final double consumedPowerMah = batteryConsumer.getConsumedPower(key);
+ final float powerMah =
+ clampPowerMah(
+ consumedPowerMah, "uid-" + uid + "-" + powerComponentName);
final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
-
if (powerMah == 0 && powerComponentDurationMillis == 0) {
return true;
}
@@ -3016,6 +3100,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
if (Flags.streamlinedBatteryStats()) {
pw.println(" --sample: collect and dump a sample of stats for debugging purpose");
}
+ if (isBatteryUsageStatsAccumulationSupported()) {
+ pw.println(" --accumulated: continuously accumulated since setup or reset-all");
+ }
pw.println(" <package.name>: optional name of package to filter output by.");
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
@@ -3082,8 +3169,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
- private void dumpUsageStats(FileDescriptor fd, PrintWriter pw, int model,
- boolean proto) {
+ private void dumpUsageStats(FileDescriptor fd, PrintWriter pw,
+ boolean proto, boolean accumulated) {
awaitCompletion();
syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
@@ -3094,8 +3181,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
if (Flags.batteryUsageStatsByPowerAndScreenState()) {
builder.includeScreenStateData().includePowerStateData();
}
- if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- builder.powerProfileModeledOnly();
+ if (accumulated) {
+ builder.accumulated();
}
BatteryUsageStatsQuery query = builder.build();
synchronized (mStats) {
@@ -3107,12 +3194,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.collectPowerStatsSamples();
}
- BatteryUsageStats batteryUsageStats =
- mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query);
- if (proto) {
- batteryUsageStats.dumpToProto(fd);
- } else {
- batteryUsageStats.dump(pw, " ");
+ try (BatteryUsageStats batteryUsageStats =
+ mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query)) {
+ if (proto) {
+ batteryUsageStats.dumpToProto(fd);
+ } else {
+ batteryUsageStats.dump(pw, " ");
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot close BatteryUsageStats", e);
}
}
@@ -3285,33 +3375,19 @@ public final class BatteryStatsService extends IBatteryStats.Stub
dumpPowerProfile(pw);
return;
} else if ("--usage".equals(arg)) {
- int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
boolean proto = false;
+ boolean accumulated = false;
for (int j = i + 1; j < args.length; j++) {
switch (args[j]) {
case "--proto":
proto = true;
break;
- case "--model": {
- if (j + 1 < args.length) {
- j++;
- if ("power-profile".equals(args[j])) {
- model = BatteryConsumer.POWER_MODEL_POWER_PROFILE;
- } else {
- pw.println("Unknown power model: " + args[j]);
- dumpHelp(pw);
- return;
- }
- } else {
- pw.println("--model without a value");
- dumpHelp(pw);
- return;
- }
+ case "--accumulated":
+ accumulated = true;
break;
- }
}
}
- dumpUsageStats(fd, pw, model, proto);
+ dumpUsageStats(fd, pw, proto, accumulated);
return;
} else if ("--wakeups".equals(arg)) {
mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "),
@@ -3622,24 +3698,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
android.Manifest.permission.BATTERY_STATS, null);
}
- Future future;
if (shouldCollectExternalStats()) {
- future = mWorker.scheduleSync("get-health-stats-for-uids",
+ mWorker.scheduleSync("get-health-stats-for-uids",
BatteryExternalStatsWorker.UPDATE_ALL);
- } else {
- future = null;
}
mHandler.post(() -> {
- if (future != null) {
- try {
- // Worker uses a separate thread pool, so waiting here won't cause a deadlock
- future.get();
- } catch (InterruptedException | ExecutionException e) {
- Slog.e(TAG, "Sync failed", e);
- }
- }
-
final long ident = Binder.clearCallingIdentity();
int i = -1;
try {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 15f1085b7125..354f281551b2 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -184,6 +184,13 @@ class BroadcastController {
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
/**
+ * If {@code false} invalidate the list of {@link android.os.IpcDataCache} present inside the
+ * {@link BroadcastStickyCache} class.
+ * The invalidation is required to start caching of the sticky broadcast in the client side.
+ */
+ private volatile boolean mAreStickyCachesInvalidated = false;
+
+ /**
* Resolver for broadcast intents to registered receivers.
* Holds BroadcastFilter (subclass of IntentFilter).
*/
@@ -258,6 +265,7 @@ class BroadcastController {
final StringBuilder sb = new StringBuilder("registerReceiver: ");
sb.append(Binder.getCallingUid()); sb.append('/');
sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+ sb.append("p:"); sb.append(filter.getPriority()); sb.append('/');
final int actionsCount = filter.safeCountActions();
if (actionsCount > 0) {
for (int i = 0; i < actionsCount; ++i) {
@@ -288,6 +296,11 @@ class BroadcastController {
IIntentReceiver receiver, IntentFilter filter, String permission,
int userId, int flags) {
mService.enforceNotIsolatedCaller("registerReceiver");
+
+ if (!mAreStickyCachesInvalidated) {
+ BroadcastStickyCache.invalidateAllCaches();
+ mAreStickyCachesInvalidated = true;
+ }
ArrayList<StickyBroadcast> stickyBroadcasts = null;
ProcessRecord callerApp = null;
final boolean visibleToInstantApps =
@@ -553,7 +566,7 @@ class BroadcastController {
}
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
- exported);
+ exported, callerApp.info, mService.mPlatformCompat);
if (rl.containsFilter(filter)) {
Slog.w(TAG, "Receiver with filter " + filter
+ " already registered for pid " + rl.pid
@@ -592,7 +605,7 @@ class BroadcastController {
originalStickyCallingUid, BackgroundStartPrivileges.NONE,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
null /* filterExtrasForReceiver */,
- broadcast.originalCallingAppProcessState);
+ broadcast.originalCallingAppProcessState, mService.mPlatformCompat);
queue.enqueueBroadcastLocked(r);
}
}
@@ -678,6 +691,21 @@ class BroadcastController {
}
}
+ List<IntentFilter> getRegisteredIntentFilters(IIntentReceiver receiver) {
+ synchronized (mService) {
+ final ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+ if (rl == null) {
+ return null;
+ }
+ final ArrayList<IntentFilter> filters = new ArrayList<>();
+ final int count = rl.size();
+ for (int i = 0; i < count; ++i) {
+ filters.add(rl.get(i));
+ }
+ return filters;
+ }
+ }
+
int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
@@ -685,8 +713,8 @@ class BroadcastController {
String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
mService.enforceNotIsolatedCaller("broadcastIntent");
+ final int result;
- int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -719,8 +747,9 @@ class BroadcastController {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
+
if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
- BroadcastStickyCache.incrementVersion(intent.getAction());
+ BroadcastStickyCache.invalidateCache(intent.getAction());
}
return result;
}
@@ -733,7 +762,7 @@ class BroadcastController {
boolean serialized, boolean sticky, int userId,
BackgroundStartPrivileges backgroundStartPrivileges,
@Nullable int[] broadcastAllowList) {
- int result;
+ final int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -741,7 +770,7 @@ class BroadcastController {
String[] requiredPermissions = requiredPermission == null ? null
: new String[] {requiredPermission};
try {
- result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+ result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
resultToApp, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
uid, realCallingUid, realCallingPid, userId,
@@ -751,8 +780,9 @@ class BroadcastController {
Binder.restoreCallingIdentity(origId);
}
}
+
if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
- BroadcastStickyCache.incrementVersion(intent.getAction());
+ BroadcastStickyCache.invalidateCache(intent.getAction());
}
return result;
}
@@ -1453,7 +1483,7 @@ class BroadcastController {
list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
callingUid, callerAppProcessState, resolvedType));
}
- BroadcastStickyCache.incrementVersion(intent.getAction());
+ BroadcastStickyCache.invalidateCache(intent.getAction());
}
}
@@ -1615,7 +1645,7 @@ class BroadcastController {
receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId,
backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
- callerAppProcessState);
+ callerAppProcessState, mService.mPlatformCompat);
broadcastSentEventRecord.setBroadcastRecord(r);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
@@ -1745,7 +1775,7 @@ class BroadcastController {
}
}
for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
- BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
+ BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
}
}
@@ -1909,9 +1939,7 @@ class BroadcastController {
private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }
-
- private List<ResolveInfo> collectReceiverComponents(
+ }private List<ResolveInfo> collectReceiverComponents(
Intent intent, String resolvedType, int callingUid, int callingPid,
int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
@@ -2137,7 +2165,7 @@ class BroadcastController {
mStickyBroadcasts.remove(userId);
}
for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
- BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
+ BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index adb2392a8484..83276391493f 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -16,17 +16,33 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.os.Binder;
+import android.os.UserHandle;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.compat.PlatformCompat;
+
import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
public final class BroadcastFilter extends IntentFilter {
+ /**
+ * Limit priority values defined by non-system apps to
+ * ({@link IntentFilter#SYSTEM_LOW_PRIORITY}, {@link IntentFilter#SYSTEM_HIGH_PRIORITY}).
+ */
+ @ChangeId
+ @VisibleForTesting
+ static final long RESTRICT_PRIORITY_VALUES = 371309185L;
+
// Back-pointer to the list this filter is in.
final ReceiverList receiverList;
final String packageName;
@@ -38,11 +54,12 @@ public final class BroadcastFilter extends IntentFilter {
final boolean instantApp;
final boolean visibleToInstantApp;
public final boolean exported;
+ final int initialPriority;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
String _packageName, String _featureId, String _receiverId, String _requiredPermission,
int _owningUid, int _userId, boolean _instantApp, boolean _visibleToInstantApp,
- boolean _exported) {
+ boolean _exported, ApplicationInfo _applicationInfo, PlatformCompat platformCompat) {
super(_filter);
receiverList = _receiverList;
packageName = _packageName;
@@ -54,6 +71,9 @@ public final class BroadcastFilter extends IntentFilter {
instantApp = _instantApp;
visibleToInstantApp = _visibleToInstantApp;
exported = _exported;
+ initialPriority = getPriority();
+ setPriority(calculateAdjustedPriority(owningUid, initialPriority,
+ _applicationInfo, platformCompat));
}
public @Nullable String getReceiverClassName() {
@@ -66,6 +86,10 @@ public final class BroadcastFilter extends IntentFilter {
return null;
}
+ public @NonNull ApplicationInfo getApplicationInfo() {
+ return receiverList.app.info;
+ }
+
@NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
@@ -100,6 +124,34 @@ public final class BroadcastFilter extends IntentFilter {
if (requiredPermission != null) {
pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
}
+ if (initialPriority != getPriority()) {
+ pw.print(prefix); pw.print("initialPriority="); pw.println(initialPriority);
+ }
+ }
+
+ @VisibleForTesting
+ static int calculateAdjustedPriority(int owningUid, int priority,
+ ApplicationInfo applicationInfo, PlatformCompat platformCompat) {
+ if (!Flags.restrictPriorityValues()) {
+ return priority;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!platformCompat.isChangeEnabledInternalNoLogging(
+ RESTRICT_PRIORITY_VALUES, applicationInfo)) {
+ return priority;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (!UserHandle.isCore(owningUid)) {
+ if (priority >= SYSTEM_HIGH_PRIORITY) {
+ return SYSTEM_HIGH_PRIORITY - 1;
+ } else if (priority <= SYSTEM_LOW_PRIORITY) {
+ return SYSTEM_LOW_PRIORITY + 1;
+ }
+ }
+ return priority;
}
public String toString() {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index f908c67d7ec9..8d0805d3fa13 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -42,11 +42,13 @@ import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.BroadcastOptions.DeliveryGroupPolicy;
+import android.compat.annotation.ChangeId;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
@@ -59,6 +61,7 @@ import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.compat.PlatformCompat;
import dalvik.annotation.optimization.NeverCompile;
@@ -77,6 +80,14 @@ import java.util.function.BiFunction;
* An active intent broadcast.
*/
final class BroadcastRecord extends Binder {
+ /**
+ * Limit the scope of the priority values to the process level. This means that priority values
+ * will only influence the order of broadcast delivery within the same process.
+ */
+ @ChangeId
+ @VisibleForTesting
+ static final long LIMIT_PRIORITY_SCOPE = 371307720L;
+
final @NonNull Intent intent; // the original intent that generated us
final @Nullable ComponentName targetComp; // original component name set on the intent
final @Nullable ProcessRecord callerApp; // process that sent this
@@ -417,13 +428,13 @@ final class BroadcastRecord extends Binder {
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- int callerAppProcessState) {
+ int callerAppProcessState, PlatformCompat platformCompat) {
this(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid,
callingUid, callerInstantApp, resolvedType, requiredPermissions,
excludedPermissions, excludedPackages, appOp, options, receivers, resultToApp,
resultTo, resultCode, resultData, resultExtras, serialized, sticky,
initialSticky, userId, -1, backgroundStartPrivileges, timeoutExempt,
- filterExtrasForReceiver, callerAppProcessState);
+ filterExtrasForReceiver, callerAppProcessState, platformCompat);
}
BroadcastRecord(BroadcastQueue _queue,
@@ -439,7 +450,7 @@ final class BroadcastRecord extends Binder {
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- int callerAppProcessState) {
+ int callerAppProcessState, PlatformCompat platformCompat) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -466,7 +477,8 @@ final class BroadcastRecord extends Binder {
urgent = calculateUrgent(_intent, _options);
deferUntilActive = calculateDeferUntilActive(_callingUid,
_options, _resultTo, _serialized, urgent);
- blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
+ blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(
+ receivers, _serialized, platformCompat);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -730,7 +742,8 @@ final class BroadcastRecord extends Binder {
}
/**
- * Determine if the result of {@link #calculateBlockedUntilBeyondCount(List, boolean)}
+ * Determine if the result of
+ * {@link #calculateBlockedUntilBeyondCount(List, boolean, PlatformCompat)}
* has prioritized tranches of receivers.
*/
@VisibleForTesting
@@ -754,37 +767,131 @@ final class BroadcastRecord extends Binder {
*/
@VisibleForTesting
static @NonNull int[] calculateBlockedUntilBeyondCount(
- @NonNull List<Object> receivers, boolean ordered) {
+ @NonNull List<Object> receivers, boolean ordered, PlatformCompat platformCompat) {
final int N = receivers.size();
final int[] blockedUntilBeyondCount = new int[N];
- int lastPriority = 0;
- int lastPriorityIndex = 0;
- for (int i = 0; i < N; i++) {
- if (ordered) {
- // When sending an ordered broadcast, we need to block this
- // receiver until all previous receivers have terminated
+ if (ordered) {
+ // When sending an ordered broadcast, we need to block this
+ // receiver until all previous receivers have terminated
+ for (int i = 0; i < N; i++) {
blockedUntilBeyondCount[i] = i;
+ }
+ } else {
+ if (Flags.limitPriorityScope()) {
+ final boolean[] changeEnabled = calculateChangeStateForReceivers(
+ receivers, LIMIT_PRIORITY_SCOPE, platformCompat);
+
+ // Priority of the previous tranche
+ int lastTranchePriority = 0;
+ // Priority of the current tranche
+ int currentTranchePriority = 0;
+ // Index of the last receiver in the previous tranche
+ int lastTranchePriorityIndex = -1;
+ // Index of the last receiver with change disabled in the previous tranche
+ int lastTrancheChangeDisabledIndex = -1;
+ // Index of the last receiver with change disabled in the current tranche
+ int currentTrancheChangeDisabledIndex = -1;
+
+ for (int i = 0; i < N; i++) {
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if (i == 0) {
+ currentTranchePriority = thisPriority;
+ if (!changeEnabled[i]) {
+ currentTrancheChangeDisabledIndex = i;
+ }
+ continue;
+ }
+
+ // Check if a new priority tranche has started
+ if (thisPriority != currentTranchePriority) {
+ // Update tranche boundaries and reset the disabled index.
+ if (currentTrancheChangeDisabledIndex != -1) {
+ lastTrancheChangeDisabledIndex = currentTrancheChangeDisabledIndex;
+ }
+ lastTranchePriority = currentTranchePriority;
+ lastTranchePriorityIndex = i - 1;
+ currentTranchePriority = thisPriority;
+ currentTrancheChangeDisabledIndex = -1;
+ }
+ if (!changeEnabled[i]) {
+ currentTrancheChangeDisabledIndex = i;
+
+ // Since the change is disabled, block the current receiver until the
+ // last receiver in the previous tranche.
+ blockedUntilBeyondCount[i] = lastTranchePriorityIndex + 1;
+ } else if (thisPriority != lastTranchePriority) {
+ // If the changeId was disabled for an earlier receiver and the current
+ // receiver has a different priority, block the current receiver
+ // until that earlier receiver.
+ if (lastTrancheChangeDisabledIndex != -1) {
+ blockedUntilBeyondCount[i] = lastTrancheChangeDisabledIndex + 1;
+ }
+ }
+ }
+ // If the entire list is in the same priority tranche or no receivers had
+ // changeId disabled, mark as -1 to indicate that none of them need to wait
+ if (N > 0 && (lastTranchePriorityIndex == -1
+ || (lastTrancheChangeDisabledIndex == -1
+ && currentTrancheChangeDisabledIndex == -1))) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
+ }
} else {
// When sending a prioritized broadcast, we only need to wait
// for the previous tranche of receivers to be terminated
- final int thisPriority = getReceiverPriority(receivers.get(i));
- if ((i == 0) || (thisPriority != lastPriority)) {
- lastPriority = thisPriority;
- lastPriorityIndex = i;
- blockedUntilBeyondCount[i] = i;
- } else {
- blockedUntilBeyondCount[i] = lastPriorityIndex;
+ int lastPriority = 0;
+ int lastPriorityIndex = 0;
+ for (int i = 0; i < N; i++) {
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if ((i == 0) || (thisPriority != lastPriority)) {
+ lastPriority = thisPriority;
+ lastPriorityIndex = i;
+ blockedUntilBeyondCount[i] = i;
+ } else {
+ blockedUntilBeyondCount[i] = lastPriorityIndex;
+ }
+ }
+ // If the entire list is in the same priority tranche, mark as -1 to
+ // indicate that none of them need to wait
+ if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
}
}
}
- // If the entire list is in the same priority tranche, mark as -1 to
- // indicate that none of them need to wait
- if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
- Arrays.fill(blockedUntilBeyondCount, -1);
- }
return blockedUntilBeyondCount;
}
+ @VisibleForTesting
+ static @NonNull boolean[] calculateChangeStateForReceivers(@NonNull List<Object> receivers,
+ long changeId, PlatformCompat platformCompat) {
+ // TODO: b/371307720 - Remove this method as we are already avoiding the packagemanager
+ // calls by checking the changeId state using ApplicationInfos.
+ final ArrayMap<String, Boolean> changeStates = new ArrayMap<>();
+ final int count = receivers.size();
+ final boolean[] changeStateForReceivers = new boolean[count];
+ for (int i = 0; i < count; ++i) {
+ final ApplicationInfo receiverAppInfo = getReceiverAppInfo(receivers.get(i));
+ final boolean isChangeEnabled;
+ final int idx = changeStates.indexOfKey(receiverAppInfo.packageName);
+ if (idx >= 0) {
+ isChangeEnabled = changeStates.valueAt(idx);
+ } else {
+ isChangeEnabled = platformCompat.isChangeEnabledInternalNoLogging(
+ changeId, receiverAppInfo);
+ changeStates.put(receiverAppInfo.packageName, isChangeEnabled);
+ }
+ changeStateForReceivers[i] = isChangeEnabled;
+ }
+ return changeStateForReceivers;
+ }
+
+ static ApplicationInfo getReceiverAppInfo(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).getApplicationInfo();
+ } else {
+ return ((ResolveInfo) receiver).activityInfo.applicationInfo;
+ }
+ }
+
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index f4a931f89551..d2af84cf3d30 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -19,7 +19,6 @@ package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerService.checkComponentPermission;
import static com.android.server.am.BroadcastQueue.TAG;
-import static com.android.server.am.Flags.usePermissionManagerForBroadcastDeliveryCheck;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -289,33 +288,16 @@ public class BroadcastSkipPolicy {
if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource[] attributionSources;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSources = createAttributionSourcesForResolveInfo(info);
- } else {
- attributionSources = null;
- }
+ final AttributionSource[] attributionSources =
+ createAttributionSourcesForResolveInfo(info);
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- try {
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- perm = hasPermissionForDataDelivery(
- requiredPermission,
- "Broadcast delivered to " + info.activityInfo.name,
- attributionSources)
- ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED;
- } else {
- perm = AppGlobals.getPackageManager()
- .checkPermission(
- requiredPermission,
- info.activityInfo.applicationInfo.packageName,
- UserHandle
- .getUserId(info.activityInfo.applicationInfo.uid));
- }
- } catch (RemoteException e) {
- perm = PackageManager.PERMISSION_DENIED;
- }
+ perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to " + info.activityInfo.name,
+ attributionSources)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent + " to "
@@ -324,15 +306,6 @@ public class BroadcastSkipPolicy {
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- if (!usePermissionManagerForBroadcastDeliveryCheck()) {
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
- if (!noteOpForManifestReceiver(appOp, r, info, component)) {
- return "Skipping delivery to " + info.activityInfo.packageName
- + " due to required appop " + appOp;
- }
- }
- }
}
}
if (r.appOp != AppOpsManager.OP_NONE) {
@@ -452,35 +425,20 @@ public class BroadcastSkipPolicy {
// Check that the receiver has the required permission(s) to receive this broadcast.
if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource attributionSource;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSource =
- new AttributionSource.Builder(filter.receiverList.uid)
- .setPid(filter.receiverList.pid)
- .setPackageName(filter.packageName)
- .setAttributionTag(filter.featureId)
- .build();
- } else {
- attributionSource = null;
- }
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(filter.receiverList.uid)
+ .setPid(filter.receiverList.pid)
+ .setPackageName(filter.packageName)
+ .setAttributionTag(filter.featureId)
+ .build();
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- final int perm;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- perm = hasPermissionForDataDelivery(
- requiredPermission,
- "Broadcast delivered to registered receiver " + filter.receiverId,
- attributionSource)
- ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED;
- } else {
- perm = checkComponentPermission(
- requiredPermission,
- filter.receiverList.pid,
- filter.receiverList.uid,
- -1 /* owningUid */,
- true /* exported */);
- }
+ final int perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to registered receiver " + filter.receiverId,
+ attributionSource)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent.toString()
@@ -491,24 +449,6 @@ public class BroadcastSkipPolicy {
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- if (!usePermissionManagerForBroadcastDeliveryCheck()) {
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
- && mService.getAppOpsManager().noteOpNoThrow(appOp,
- filter.receiverList.uid, filter.packageName, filter.featureId,
- "Broadcast delivered to registered receiver " + filter.receiverId)
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: receiving "
- + r.intent.toString()
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")"
- + " requires appop " + AppOpsManager.permissionToOp(
- requiredPermission)
- + " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")";
- }
- }
}
}
if ((r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 2323fe9d8abe..2f5362f53361 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -101,7 +101,7 @@ import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executor;
-public final class CachedAppOptimizer {
+public class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index ae5ae0133e1b..4f0ea51bac2a 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -40,7 +40,7 @@ public final class ContentProviderConnection extends Binder implements
public final String clientPackage;
public AssociationState.SourceState association;
public final long createTime;
- private Object mProcStatsLock; // Internal lock for accessing AssociationState
+ private volatile Object mProcStatsLock; // Internal lock for accessing AssociationState
/**
* Internal lock that guards access to the two counters.
@@ -118,19 +118,25 @@ public final class ContentProviderConnection extends Binder implements
* Track the given proc state change.
*/
public void trackProcState(int procState, int seq) {
- if (association != null) {
- synchronized (mProcStatsLock) {
+ if (association == null) {
+ return; // early exit to optimize on oomadj cycles
+ }
+ synchronized (mProcStatsLock) {
+ if (association != null) { // due to race-conditions, association may have become null
association.trackProcState(procState, seq, SystemClock.uptimeMillis());
}
}
}
public void stopAssociation() {
- if (association != null) {
- synchronized (mProcStatsLock) {
+ if (association == null) {
+ return; // early exit to optimize on oomadj cycles
+ }
+ synchronized (mProcStatsLock) {
+ if (association != null) { // due to race-conditions, association may have become null
association.stop();
+ association = null;
}
- association = null;
}
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f72fdf84446b..e4e53f4124f3 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -90,7 +90,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.RescueParty;
+import com.android.server.crashrecovery.CrashRecoveryAdaptor;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
@@ -325,7 +325,8 @@ public class ContentProviderHelper {
final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
boolean success = !serviceBindingOomAdjPolicy()
|| mService.mOomAdjuster.evaluateProviderConnectionAdd(r, cpr.proc)
- ? mService.updateOomAdjLocked(cpr.proc, OOM_ADJ_REASON_GET_PROVIDER)
+ ? mService.mProcessStateController.runUpdate(cpr.proc,
+ OOM_ADJ_REASON_GET_PROVIDER)
: true;
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
@@ -534,10 +535,9 @@ public class ContentProviderHelper {
if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
Slog.d(TAG, "Installing in existing process " + proc);
}
- final ProcessProviderRecord pr = proc.mProviders;
- if (!pr.hasProvider(cpi.name)) {
+ if (mService.mProcessStateController.addPublishedProvider(proc,
+ cpi.name, cpr)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
- pr.installProvider(cpi.name, cpr);
mService.mOomAdjuster.unfreezeTemporarily(proc,
CachedAppOptimizer.UNFREEZE_REASON_GET_PROVIDER);
try {
@@ -881,7 +881,8 @@ public class ContentProviderHelper {
ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
if (localCpr.hasExternalProcessHandles()) {
- if (localCpr.removeExternalProcessHandleLocked(token)) {
+ if (mService.mProcessStateController.removeExternalProviderClient(localCpr,
+ token)) {
mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
} else {
Slog.e(TAG, "Attempt to remove content provider " + localCpr
@@ -1381,7 +1382,7 @@ public class ContentProviderHelper {
mService.mOomAdjuster.initSettings();
// Now that the settings provider is published we can consider sending in a rescue party.
- RescueParty.onSettingsProviderPublished(mService.mContext);
+ CrashRecoveryAdaptor.rescuePartyOnSettingsProviderPublished(mService.mContext);
}
/**
@@ -1447,7 +1448,8 @@ public class ContentProviderHelper {
String callingPackage, String callingTag, boolean stable, boolean updateLru,
long startTime, ProcessList processList, @UserIdInt int expectedUserId) {
if (r == null) {
- cpr.addExternalProcessHandleLocked(externalProcessToken, callingUid, callingTag);
+ mService.mProcessStateController.addExternalProviderClient(cpr, externalProcessToken,
+ callingUid, callingTag);
return null;
}
@@ -1470,7 +1472,7 @@ public class ContentProviderHelper {
if (cpr.proc != null) {
cpr.proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_PROVIDER);
}
- pr.addProviderConnection(conn);
+ mService.mProcessStateController.addProviderConnection(r, conn);
mService.startAssociationLocked(r.uid, r.processName, r.mState.getCurProcState(),
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
if (updateLru && cpr.proc != null
@@ -1493,7 +1495,8 @@ public class ContentProviderHelper {
ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable,
boolean enforceDelay, boolean updateOomAdj) {
if (conn == null) {
- cpr.removeExternalProcessHandleLocked(externalProcessToken);
+ mService.mProcessStateController.removeExternalProviderClient(cpr,
+ externalProcessToken);
return false;
}
@@ -1537,14 +1540,15 @@ public class ContentProviderHelper {
if (cpr.proc != null && !hasProviderConnectionLocked(cpr.proc)) {
cpr.proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_PROVIDER);
}
- conn.client.mProviders.removeProviderConnection(conn);
+ mService.mProcessStateController.removeProviderConnection(conn.client, conn);
if (conn.client.mState.getSetProcState()
< ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
// The client is more important than last activity -- note the time this
// is happening, so we keep the old provider process around a bit as last
// activity to avoid thrashing it.
if (cpr.proc != null) {
- cpr.proc.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
+ mService.mProcessStateController.setLastProviderTime(cpr.proc,
+ SystemClock.uptimeMillis());
}
}
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
@@ -1821,7 +1825,7 @@ public class ContentProviderHelper {
}
}
if (removed && cpr.proc != null) {
- cpr.proc.mProviders.removeProvider(cpr.info.name);
+ mService.mProcessStateController.removePublishedProvider(cpr.proc, cpr.info.name);
}
}
diff --git a/services/core/java/com/android/server/am/FgsTempAllowList.java b/services/core/java/com/android/server/am/FgsTempAllowList.java
index c28655655765..5569b6db3285 100644
--- a/services/core/java/com/android/server/am/FgsTempAllowList.java
+++ b/services/core/java/com/android/server/am/FgsTempAllowList.java
@@ -29,7 +29,7 @@ import java.util.function.BiConsumer;
/**
* List of keys that have expiration time.
- * If the expiration time is less than current elapsedRealtime, the key has expired.
+ * If the expiration time is less than current uptime, the key has expired.
* Otherwise it is valid (or allowed).
*
* <p>This is used for both FGS-BG-start restriction, and FGS-while-in-use permissions check.</p>
@@ -42,7 +42,7 @@ public class FgsTempAllowList<E> {
private static final int DEFAULT_MAX_SIZE = 100;
/**
- * The value is Pair type, Pair.first is the expirationTime(an elapsedRealtime),
+ * The value is Pair type, Pair.first is the expirationTime(in cpu uptime),
* Pair.second is the optional information entry about this key.
*/
private final SparseArray<Pair<Long, E>> mTempAllowList = new SparseArray<>();
@@ -82,7 +82,9 @@ public class FgsTempAllowList<E> {
}
// The temp allowlist should be a short list with only a few entries in it.
// for a very large list, HashMap structure should be used.
- final long now = SystemClock.elapsedRealtime();
+ final long now = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist()
+ ? SystemClock.uptimeMillis()
+ : SystemClock.elapsedRealtime();
final int size = mTempAllowList.size();
if (size > mMaxSize) {
Slog.w(TAG_AM, "FgsTempAllowList length:" + size + " exceeds maxSize"
@@ -112,12 +114,15 @@ public class FgsTempAllowList<E> {
final int index = mTempAllowList.indexOfKey(uid);
if (index < 0) {
return null;
- } else if (mTempAllowList.valueAt(index).first < SystemClock.elapsedRealtime()) {
+ }
+ final long timeNow = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist()
+ ? SystemClock.uptimeMillis()
+ : SystemClock.elapsedRealtime();
+ if (mTempAllowList.valueAt(index).first < timeNow) {
mTempAllowList.removeAt(index);
return null;
- } else {
- return mTempAllowList.valueAt(index);
}
+ return mTempAllowList.valueAt(index);
}
}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 534247ec082a..4b6d6bc955cc 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -50,13 +50,15 @@ per-file User* = file:/MULTIUSER_OWNERS
# Broadcasts
per-file Broadcast* = file:/BROADCASTS_OWNERS
+per-file broadcasts_flags.aconfig = file:/BROADCASTS_OWNERS
# Permissions & Packages
per-file *Permission* = patb@google.com
per-file *Package* = patb@google.com
-# OOM Adjuster
+# OOM Adjuster & ProcessStateController
per-file *Oom* = file:/OOM_ADJUSTER_OWNERS
+per-file ProcessStateController.java = file:/OOM_ADJUSTER_OWNERS
# Miscellaneous
per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 78a0a117fe6f..f42641ece09b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -19,6 +19,7 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT;
import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
@@ -78,7 +79,6 @@ import static android.os.Process.THREAD_GROUP_RESTRICTED;
import static android.os.Process.THREAD_GROUP_TOP_APP;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
-import static android.os.Process.setProcessGroup;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
@@ -115,6 +115,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -130,6 +131,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
@@ -154,6 +156,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -376,6 +379,7 @@ public class OomAdjuster {
final ActivityManagerService mService;
final Injector mInjector;
+ final GlobalState mGlobalState;
final ProcessList mProcessList;
final ActivityManagerGlobalLock mProcLock;
@@ -458,9 +462,27 @@ public class OomAdjuster {
}
void setThreadPriority(int tid, int priority) {
+ if (Flags.resetOnForkEnabled()) {
+ if (Process.getThreadScheduler(tid) == Process.SCHED_OTHER) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
+ 0);
+ }
+ }
Process.setThreadPriority(tid, priority);
}
+ }
+ // TODO(b/346822474): hook up global state usage.
+ interface GlobalState {
+ /** Is device's screen on. */
+ boolean isAwake();
+
+ /** What process is running a backup for a given userId. */
+ ProcessRecord getBackupTarget(@UserIdInt int userId);
+
+ /** Is memory level normal since last evaluation. */
+ boolean isLastMemoryLevelNormal();
}
boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -468,10 +490,6 @@ public class OomAdjuster {
return mInjector.isChangeEnabled(cachedCompatChangeId, app, defaultValue);
}
- OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
- this(service, processList, activeUids, createAdjusterThread());
- }
-
static ServiceThread createAdjusterThread() {
// The process group is usually critical to the response time of foreground app, so the
// setter should apply it as soon as possible.
@@ -482,51 +500,27 @@ public class OomAdjuster {
}
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- ServiceThread adjusterThread) {
- this(service, processList, activeUids, adjusterThread, new Injector());
- }
-
- OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- Injector injector) {
- this(service, processList, activeUids, createAdjusterThread(), injector);
- }
-
- OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- ServiceThread adjusterThread, Injector injector) {
+ ServiceThread adjusterThread, GlobalState globalState,
+ CachedAppOptimizer cachedAppOptimizer, Injector injector) {
mService = service;
+ mGlobalState = globalState;
mInjector = injector;
mProcessList = processList;
mProcLock = service.mProcLock;
mActiveUids = activeUids;
mConstants = mService.mConstants;
- mCachedAppOptimizer = new CachedAppOptimizer(mService);
+ mCachedAppOptimizer = cachedAppOptimizer;
mCacheOomRanker = new CacheOomRanker(service);
mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
- final int pid = msg.arg1;
- final int group = msg.arg2;
- if (pid == ActivityManagerService.MY_PID) {
- // Skip setting the process group for system_server, keep it as default.
- return true;
- }
- final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- if (traceEnabled) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
- + msg.obj + " to " + group);
- }
- try {
- setProcessGroup(pid, group);
- } catch (Exception e) {
- if (DEBUG_ALL) {
- Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
- }
- } finally {
- if (traceEnabled) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
+ final int group = msg.what;
+ final ProcessRecord app = (ProcessRecord) msg.obj;
+ setProcessGroup(app.getPid(), group, app.processName);
+ if (Flags.phantomProcessesFix()) {
+ mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, group);
}
return true;
});
@@ -536,6 +530,34 @@ public class OomAdjuster {
/ CACHED_APP_IMPORTANCE_LEVELS;
}
+ void setProcessGroup(int pid, int group, String processName) {
+ if (pid == ActivityManagerService.MY_PID) {
+ // Skip setting the process group for system_server, keep it as default.
+ return;
+ }
+ final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (traceEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
+ + processName + " to " + group);
+ }
+ try {
+ android.os.Process.setProcessGroup(pid, group);
+ } catch (Exception e) {
+ if (DEBUG_ALL) {
+ Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
+ }
+ } finally {
+ if (traceEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+ }
+
+ void setAppAndChildProcessGroup(ProcessRecord app, int group) {
+ mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
+ group, app));
+ }
+
void initSettings() {
mCachedAppOptimizer.init();
mCacheOomRanker.init(ActivityThread.currentApplication().getMainExecutor());
@@ -684,7 +706,7 @@ public class OomAdjuster {
// In case the app goes from non-cached to cached but it doesn't have other reachable
// processes, its adj could be still unknown as of now, assign one.
processes.add(app);
- assignCachedAdjIfNecessary(processes);
+ applyLruAdjust(processes);
applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
mInjector.getElapsedRealtimeMillis(), oomAdjReason);
}
@@ -1074,7 +1096,7 @@ public class OomAdjuster {
}
mProcessesInCycle.clear();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
@@ -1136,35 +1158,41 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+ protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
+ int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
if (mConstants.USE_TIERED_CACHED_ADJ) {
final long now = mInjector.getUptimeMillis();
int uiTargetAdj = 10;
+ // mConstants.TIERED_CACHED_ADJ_UI_TIER_SIZE is 10 by default, but is configurable.
+ final int uiTierMaxAdj = 10 + mConstants.TIERED_CACHED_ADJ_UI_TIER_SIZE;
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
- if (!app.isKilledByAm() && app.getThread() != null
- && (state.getCurAdj() >= UNKNOWN_ADJ
- || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+ || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
final ProcessServiceRecord psr = app.mServices;
int targetAdj = CACHED_APP_MIN_ADJ;
if (opt != null && opt.isFreezeExempt()) {
// BIND_WAIVE_PRIORITY and the like get oom_adj 900
targetAdj += 0;
- } else if (state.hasShownUi() && uiTargetAdj < 15) {
- // The most recent 5 apps that have shown UI get 910-914
+ } else if (state.hasShownUi() && uiTargetAdj < uiTierMaxAdj) {
+ // The most recent UI-showing apps get [910, 910 + ui tier size).
targetAdj += uiTargetAdj++;
} else if ((state.getSetAdj() >= CACHED_APP_MIN_ADJ)
&& (state.getLastStateTime()
+ mConstants.TIERED_CACHED_ADJ_DECAY_TIME) < now) {
- // Older cached apps get 950
- targetAdj += 50;
+ // Older cached apps get 940 + ui tier size (950 by default).
+ targetAdj += 40 + mConstants.TIERED_CACHED_ADJ_UI_TIER_SIZE;
} else {
- // Newer cached apps get 920
- targetAdj += 20;
+ // Newer cached apps get 910 + ui tier size (920 by default).
+ targetAdj += 10 + mConstants.TIERED_CACHED_ADJ_UI_TIER_SIZE;
}
state.setCurRawAdj(targetAdj);
state.setCurAdj(psr.modifyRawOomAdj(targetAdj));
@@ -1214,10 +1242,13 @@ public class OomAdjuster {
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
- // If we haven't yet assigned the final cached adj
- // to the process, do that now.
- if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
- >= UNKNOWN_ADJ) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null
+ && curAdj >= UNKNOWN_ADJ) {
+ // If we haven't yet assigned the final cached adj to the process, do that now.
final ProcessServiceRecord psr = app.mServices;
switch (state.getCurProcState()) {
case PROCESS_STATE_LAST_ACTIVITY:
@@ -1345,9 +1376,11 @@ public class OomAdjuster {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
if (!app.isKilledByAm() && app.getThread() != null) {
- // We don't need to apply the update for the process which didn't get computed
- if (state.getCompletedAdjSeq() == mAdjSeq) {
- applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason, true);
+ if (!Flags.fixApplyOomadjOrder()) {
+ // We don't need to apply the update for the process which didn't get computed
+ if (state.getCompletedAdjSeq() == mAdjSeq) {
+ applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason, true);
+ }
}
if (app.isPendingFinishAttach()) {
@@ -1449,6 +1482,19 @@ public class OomAdjuster {
}
}
+ if (Flags.fixApplyOomadjOrder()) {
+ // We need to apply the update starting from the least recently used.
+ // Otherwise, they won't be in the correct LRU order in LMKD.
+ for (int i = 0; i < numLru; i++) {
+ ProcessRecord app = lruList.get(i);
+ // We don't need to apply the update for the process which didn't get computed
+ if (!app.isKilledByAm() && app.getThread() != null
+ && app.mState.getCompletedAdjSeq() == mAdjSeq) {
+ applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason, true);
+ }
+ }
+ }
+
if (!mProcsToOomAdj.isEmpty()) {
mInjector.batchSetOomAdj(mProcsToOomAdj);
mProcsToOomAdj.clear();
@@ -1809,9 +1855,36 @@ public class OomAdjuster {
}
}
+ private boolean isDeviceFullyAwake() {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ return mGlobalState.isAwake();
+ } else {
+ return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE;
+ }
+ }
+
private boolean isScreenOnOrAnimatingLocked(ProcessStateRecord state) {
- return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
- || state.isRunningRemoteAnimation();
+ return isDeviceFullyAwake() || state.isRunningRemoteAnimation();
+ }
+
+ private boolean isBackupProcess(ProcessRecord app) {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ return app == mGlobalState.getBackupTarget(app.userId);
+ } else {
+ final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
+ if (backupTarget == null) {
+ return false;
+ }
+ return app == backupTarget.app;
+ }
+ }
+
+ private boolean isLastMemoryLevelNormal() {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ return mGlobalState.isLastMemoryLevelNormal();
+ } else {
+ return mService.mAppProfiler.isLastMemoryLevelNormal();
+ }
}
@GuardedBy({"mService", "mProcLock"})
@@ -2252,8 +2325,7 @@ public class OomAdjuster {
state.setHasStartedServices(false);
state.setAdjSeq(mAdjSeq);
- final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
- if (backupTarget != null && app == backupTarget.app) {
+ if (isBackupProcess(app)) {
// If possible we want to avoid killing apps while they're being backed up
if (adj > BACKUP_APP_ADJ) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
@@ -2519,8 +2591,7 @@ public class OomAdjuster {
double cachedRestoreThreshold =
mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
- if (!mService.mAppProfiler.isLastMemoryLevelNormal()
- && lastPssOrRss >= cachedRestoreThreshold) {
+ if (!isLastMemoryLevelNormal() && lastPssOrRss >= cachedRestoreThreshold) {
state.setServiceHighRam(true);
state.setServiceB(true);
//Slog.i(TAG, "ADJ " + app + " high ram!");
@@ -2543,6 +2614,7 @@ public class OomAdjuster {
}
capability |= getDefaultCapability(app, procState);
+ capability |= getCpuCapability(app, now);
// Procstates below BFGS should never have this capability.
if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
@@ -2614,7 +2686,7 @@ public class OomAdjuster {
// Put bound foreground services in a special sched group for additional
// restrictions on screen off
if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+ && !isDeviceFullyAwake()
&& !state.shouldScheduleLikeTopApp()) {
if (schedGroup > SCHED_GROUP_RESTRICTED) {
schedGroup = SCHED_GROUP_RESTRICTED;
@@ -2685,8 +2757,12 @@ public class OomAdjuster {
if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
app.mOptRecord.shouldNotFreezeReason()
| client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
}
@@ -2697,6 +2773,8 @@ public class OomAdjuster {
// we check the final procstate, and remove it if the procsate is below BFGS.
capability |= getBfslCapabilityFromClient(client);
+ capability |= getCpuCapabilityFromClient(client);
+
if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
capability |= cstate.getCurCapability();
@@ -2755,9 +2833,14 @@ public class OomAdjuster {
app.mOptRecord.shouldNotFreezeReason()
| ProcessCachedOptimizerRecord
.SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
+ capability |= PROCESS_CAPABILITY_CPU_TIME;
}
// Not doing bind OOM management, so treat
// this guy more like a started service.
@@ -2903,8 +2986,7 @@ public class OomAdjuster {
clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
} else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- } else if (mService.mWakefulness.get()
- == PowerManagerInternal.WAKEFULNESS_AWAKE
+ } else if (isDeviceFullyAwake()
&& cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else {
@@ -3000,9 +3082,14 @@ public class OomAdjuster {
app.mOptRecord.shouldNotFreezeReason()
| ProcessCachedOptimizerRecord
.SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
+ capability |= PROCESS_CAPABILITY_CPU_TIME;
}
}
if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
@@ -3055,9 +3142,24 @@ public class OomAdjuster {
capability &= ~PROCESS_CAPABILITY_BFSL;
}
if (!updated) {
- updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
- || (capability != prevCapability
- && (capability & prevCapability) == prevCapability);
+ if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+ updated = true;
+ }
+
+ if (Flags.useCpuTimeCapability()) {
+ if ((capability != prevCapability)
+ && ((capability & prevCapability) == prevCapability)) {
+ updated = true;
+ }
+ } else {
+ // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+ final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+ final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+ if ((curFiltered != prevFiltered)
+ && ((curFiltered & prevFiltered) == prevFiltered)) {
+ updated = true;
+ }
+ }
}
if (dryRun) {
@@ -3133,6 +3235,8 @@ public class OomAdjuster {
// we check the final procstate, and remove it if the procsate is below BFGS.
capability |= getBfslCapabilityFromClient(client);
+ capability |= getCpuCapabilityFromClient(client);
+
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
// If the other app is cached for any reason, for purposes here
// we are going to consider it empty.
@@ -3143,8 +3247,12 @@ public class OomAdjuster {
if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
app.mOptRecord.shouldNotFreezeReason()
| client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
}
@@ -3220,10 +3328,25 @@ public class OomAdjuster {
capability &= ~PROCESS_CAPABILITY_BFSL;
}
- if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
- || (capability != prevCapability
- && (capability & prevCapability) == prevCapability))) {
- return true;
+ if (dryRun) {
+ if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+ return true;
+ }
+
+ if (Flags.useCpuTimeCapability()) {
+ if ((capability != prevCapability)
+ && ((capability & prevCapability) == prevCapability)) {
+ return true;
+ }
+ } else {
+ // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+ final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+ final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+ if ((curFiltered != prevFiltered)
+ && ((curFiltered & prevFiltered) == prevFiltered)) {
+ return true;
+ }
+ }
}
if (adj < prevRawAdj) {
@@ -3251,7 +3374,12 @@ public class OomAdjuster {
baseCapabilities = PROCESS_CAPABILITY_ALL; // BFSL allowed
break;
case PROCESS_STATE_BOUND_TOP:
- baseCapabilities = PROCESS_CAPABILITY_BFSL;
+ if (app.getActiveInstrumentation() != null) {
+ baseCapabilities = PROCESS_CAPABILITY_BFSL |
+ PROCESS_CAPABILITY_ALL_IMPLICIT;
+ } else {
+ baseCapabilities = PROCESS_CAPABILITY_BFSL;
+ }
break;
case PROCESS_STATE_FOREGROUND_SERVICE:
if (app.getActiveInstrumentation() != null) {
@@ -3270,6 +3398,29 @@ public class OomAdjuster {
return baseCapabilities | networkCapabilities;
}
+ private static int getCpuCapability(ProcessRecord app, long nowUptime) {
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null && uidRec.isCurAllowListed()) {
+ // Process has user visible activities.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ if (UserHandle.isCore(app.uid)) {
+ // Make sure all system components are not frozen.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ if (app.mState.getCachedHasVisibleActivities()) {
+ // Process has user visible activities.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
+ // It running a short fgs, just give it cpu time.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ // TODO(b/370817323): Populate this method with all of the reasons to keep a process
+ // unfrozen.
+ return 0;
+ }
+
/**
* @return the BFSL capability from a client (of a service binding or provider).
*/
@@ -3318,6 +3469,15 @@ public class OomAdjuster {
}
/**
+ * @return the CPU capability from a client (of a service binding or provider).
+ */
+ private static int getCpuCapabilityFromClient(ProcessRecord client) {
+ // Just grant CPU capability every time
+ // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings.
+ return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME;
+ }
+
+ /**
* Checks if for the given app and client, there's a cycle that should skip over the client
* for now or use partial values to evaluate the effect of the client binding.
* @param app
@@ -3452,8 +3612,7 @@ public class OomAdjuster {
processGroup = THREAD_GROUP_DEFAULT;
break;
}
- mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
- 0 /* unused */, app.getPid(), processGroup, app.processName));
+ setAppAndChildProcessGroup(app, processGroup);
try {
final int renderThreadTid = app.getRenderThreadTid();
if (curSchedGroup == SCHED_GROUP_TOP_APP) {
@@ -3898,6 +4057,39 @@ public class OomAdjuster {
mCacheOomRanker.dump(pw);
}
+ /**
+ * Return whether or not a process should be frozen.
+ */
+ boolean getFreezePolicy(ProcessRecord proc) {
+ // Reasons to not freeze:
+ if (Flags.useCpuTimeCapability()) {
+ if ((proc.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+ /// App is important enough (see {@link #getCpuCapability}) or bound by something
+ /// important enough to not be frozen.
+ return false;
+ }
+ } else {
+ // The CPU capability handling covers all setShouldNotFreeze paths. Must check
+ // shouldNotFreeze, if the CPU capability is not being used.
+ if (proc.mOptRecord.shouldNotFreeze()) {
+ return false;
+ }
+ }
+
+ if (proc.mOptRecord.isFreezeExempt()) {
+ return false;
+ }
+
+ // Reasons to freeze:
+ if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) {
+ // Oomscore is in a high enough state, it is safe to freeze.
+ return true;
+ }
+
+ // Default, do not freeze a process.
+ return false;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
boolean immediate, int oldOomAdj) {
@@ -3912,43 +4104,44 @@ public class OomAdjuster {
(state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
|| oldOomAdj == UNKNOWN_ADJ;
final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
- if ((oomAdjChanged || shouldNotFreezeChanged)
+ final boolean hasCpuCapability =
+ (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability())
+ == PROCESS_CAPABILITY_CPU_TIME;
+ final boolean usedToHaveCpuCapability =
+ (PROCESS_CAPABILITY_CPU_TIME & app.mState.getSetCapability())
+ == PROCESS_CAPABILITY_CPU_TIME;
+ final boolean cpuCapabilityChanged = hasCpuCapability != usedToHaveCpuCapability;
+ if ((oomAdjChanged || shouldNotFreezeChanged || cpuCapabilityChanged)
&& Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER,
CachedAppOptimizer.ATRACE_FREEZER_TRACK,
"updateAppFreezeStateLSP " + app.processName
+ + " pid: " + app.getPid()
+ " isFreezeExempt: " + opt.isFreezeExempt()
+ " isFrozen: " + opt.isFrozen()
+ " shouldNotFreeze: " + opt.shouldNotFreeze()
+ " shouldNotFreezeReason: " + opt.shouldNotFreezeReason()
+ " curAdj: " + state.getCurAdj()
+ " oldOomAdj: " + oldOomAdj
- + " immediate: " + immediate);
+ + " immediate: " + immediate
+ + " cpuCapability: " + hasCpuCapability);
}
}
- if (app.mOptRecord.isFreezeExempt()) {
- return;
- }
-
- // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
- if (opt.isFrozen() && opt.shouldNotFreeze()) {
- mCachedAppOptimizer.unfreezeAppLSP(app,
- CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
- return;
- }
-
- // Use current adjustment when freezing, set adjustment when unfreezing.
- if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
- && !opt.shouldNotFreeze()) {
- if (!immediate) {
- mCachedAppOptimizer.freezeAppAsyncLSP(app);
- } else {
+ if (getFreezePolicy(app)) {
+ // This process should be frozen.
+ if (immediate && !opt.isFrozen()) {
+ // And it will be frozen immediately.
mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app);
+ } else if (!opt.isFrozen() || !opt.isPendingFreeze()) {
+ mCachedAppOptimizer.freezeAppAsyncLSP(app);
+ }
+ } else {
+ // This process should not be frozen.
+ if (opt.isFrozen() || opt.isPendingFreeze()) {
+ mCachedAppOptimizer.unfreezeAppLSP(app,
+ CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
}
- } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) {
- mCachedAppOptimizer.unfreezeAppLSP(app,
- CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
}
}
@@ -3972,7 +4165,8 @@ public class OomAdjuster {
final int size = processes.size();
for (int i = 0; i < size; i++) {
ProcessRecord proc = processes.get(i);
- mCachedAppOptimizer.unfreezeTemporarily(proc, reason);
+ mCachedAppOptimizer.unfreezeTemporarily(proc,
+ CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(reason));
}
processes.clear();
}
@@ -4025,8 +4219,9 @@ public class OomAdjuster {
}
/**
- * Evaluate the service connection, return {@code true} if the client will change the state
- * of the service host process by the given connection.
+ * Evaluate the service connection, return {@code true} if the client will change any state
+ * (ie. ProcessState, oomAdj, capability, etc) of the service host process by the given
+ * connection.
*/
@GuardedBy("mService")
boolean evaluateServiceConnectionAdd(ProcessRecord client, ProcessRecord app,
@@ -4034,20 +4229,40 @@ public class OomAdjuster {
if (evaluateConnectionPrelude(client, app)) {
return true;
}
- if (app.getSetAdj() <= client.getSetAdj()
- && app.getSetProcState() <= client.getSetProcState()
- && ((app.getSetCapability() & client.getSetCapability())
- == client.getSetCapability()
- || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES
- | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS))) {
- // The service host process has better states than the client, so no change.
- return false;
+
+ boolean needDryRun = false;
+ if (app.getSetAdj() > client.getSetAdj()) {
+ // The connection might elevate the importance of the service's oom adj score.
+ needDryRun = true;
+ } else if (app.getSetProcState() > client.getSetProcState()) {
+ // The connection might elevate the importance of the service's process state.
+ needDryRun = true;
+ } else if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES
+ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)
+ && (app.getSetCapability() & client.getSetCapability())
+ != client.getSetCapability()) {
+ // The connection might elevate the importance of the service's capabilities.
+ needDryRun = true;
+ } else if (Flags.unfreezeBindPolicyFix()
+ && cr.hasFlag(Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_ALLOW_OOM_MANAGEMENT)) {
+ // These bind flags can grant the shouldNotFreeze state to the service.
+ needDryRun = true;
+ } else if (Flags.unfreezeBindPolicyFix()
+ && client.mOptRecord.shouldNotFreeze()
+ && !app.mOptRecord.shouldNotFreeze()) {
+ // The shouldNotFreeze state can be propagated and needs to be checked.
+ needDryRun = true;
+ }
+
+ if (needDryRun) {
+ // Take a dry run of the computeServiceHostOomAdjLSP, this would't be expensive
+ // since it's only evaluating one service connection.
+ return computeServiceHostOomAdjLSP(cr, app, client, mInjector.getUptimeMillis(),
+ mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE,
+ CACHED_APP_MIN_ADJ, false, true /* dryRun */);
}
- // Take a dry run of the computeServiceHostOomAdjLSP, this would't be expensive
- // since it's only evaluating one service connection.
- return computeServiceHostOomAdjLSP(cr, app, client, mInjector.getUptimeMillis(),
- mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE,
- CACHED_APP_MIN_ADJ, false, true /* dryRun */);
+ return false;
}
@GuardedBy("mService")
@@ -4057,17 +4272,26 @@ public class OomAdjuster {
return true;
}
- if (app.getSetAdj() < client.getSetAdj()
- && app.getSetProcState() < client.getSetProcState()) {
- // The service host process has better states than the client.
- if (((app.getSetCapability() & client.getSetCapability()) == PROCESS_CAPABILITY_NONE)
- || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES
- | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
- // The service host app doesn't get any capabilities from the client.
- return false;
- }
+ if (app.getSetAdj() >= client.getSetAdj()) {
+ return true;
+ } else if (app.getSetProcState() >= client.getSetProcState()) {
+ return true;
+ } else if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES
+ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)
+ && (app.getSetCapability() & client.getSetCapability())
+ != PROCESS_CAPABILITY_NONE) {
+ return true;
+ } else if (Flags.unfreezeBindPolicyFix()
+ && cr.hasFlag(Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_ALLOW_OOM_MANAGEMENT)) {
+ return true;
+ } else if (Flags.unfreezeBindPolicyFix()
+ && app.mOptRecord.shouldNotFreeze()
+ && client.mOptRecord.shouldNotFreeze()) {
+ // Process has shouldNotFreeze and it could have gotten it from the client.
+ return true;
}
- return true;
+ return false;
}
@GuardedBy("mService")
@@ -4075,14 +4299,25 @@ public class OomAdjuster {
if (evaluateConnectionPrelude(client, app)) {
return true;
}
- if (app.getSetAdj() <= client.getSetAdj()
- && app.getSetProcState() <= client.getSetProcState()) {
- // The provider host process has better states than the client, so no change.
- return false;
+
+ boolean needDryRun = false;
+ if (app.getSetAdj() > client.getSetAdj()) {
+ needDryRun = true;
+ } else if (app.getSetProcState() > client.getSetProcState()) {
+ needDryRun = true;
+ } else if (Flags.unfreezeBindPolicyFix()
+ && client.mOptRecord.shouldNotFreeze()
+ && !app.mOptRecord.shouldNotFreeze()) {
+ needDryRun = true;
}
- return computeProviderHostOomAdjLSP(null, app, client, mInjector.getUptimeMillis(),
- mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE, CACHED_APP_MIN_ADJ,
- false, true /* dryRun */);
+
+ if (needDryRun) {
+ return computeProviderHostOomAdjLSP(null, app, client, mInjector.getUptimeMillis(),
+ mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE,
+ CACHED_APP_MIN_ADJ,
+ false, true /* dryRun */);
+ }
+ return false;
}
@GuardedBy("mService")
@@ -4090,12 +4325,19 @@ public class OomAdjuster {
if (evaluateConnectionPrelude(client, app)) {
return true;
}
- if (app.getSetAdj() < client.getSetAdj()
- && app.getSetProcState() < client.getSetProcState()) {
- // The provider host process has better states than the client, so no change.
- return false;
+
+ if (app.getSetAdj() >= client.getSetAdj()) {
+ return true;
+ } else if (app.getSetProcState() >= client.getSetProcState()) {
+ return true;
+ } else if (Flags.unfreezeBindPolicyFix()
+ && app.mOptRecord.shouldNotFreeze()
+ && client.mOptRecord.shouldNotFreeze()) {
+ // Process has shouldNotFreeze and it could have gotten it from the client.
+ return true;
}
- return true;
+
+ return false;
}
private boolean evaluateConnectionPrelude(ProcessRecord client, ProcessRecord app) {
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index fb1c2e9a1f9d..1b7e8f0bd244 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_AP
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -756,18 +757,10 @@ public class OomAdjusterModernImpl extends OomAdjuster {
new ComputeConnectionsConsumer();
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
- ActiveUids activeUids) {
- this(service, processList, activeUids, createAdjusterThread());
- }
-
- OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
- ActiveUids activeUids, ServiceThread adjusterThread) {
- super(service, processList, activeUids, adjusterThread);
- }
-
- OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
- ActiveUids activeUids, Injector injector) {
- super(service, processList, activeUids, injector);
+ ActiveUids activeUids, ServiceThread adjusterThread, GlobalState globalState,
+ CachedAppOptimizer cachedAppOptimizer, Injector injector) {
+ super(service, processList, activeUids, adjusterThread, globalState, cachedAppOptimizer,
+ injector);
}
private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
@@ -977,7 +970,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
computeConnectionsLSP();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
}
@@ -1058,20 +1051,24 @@ public class OomAdjusterModernImpl extends OomAdjuster {
// Now traverse and compute the connections of processes with changed importance.
computeConnectionsLSP();
- boolean unassignedAdj = false;
+ boolean needLruAdjust = false;
for (int i = 0, size = reachables.size(); i < size; i++) {
final ProcessStateRecord state = reachables.get(i).mState;
state.setReachable(false);
state.setCompletedAdjSeq(mAdjSeq);
- if (state.getCurAdj() >= UNKNOWN_ADJ) {
- unassignedAdj = true;
+ final int curAdj = state.getCurAdj();
+ // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+ // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+ final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+ if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+ needLruAdjust = true;
}
}
// If all processes have an assigned adj, no need to calculate and assign cached adjs.
- if (unassignedAdj) {
+ if (needLruAdjust) {
// TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
}
// Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index 3b0147cb665d..97aa947256bb 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -157,7 +157,8 @@ public class PendingIntentController {
PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,
token, resultWho, requestCode, intents, resolvedTypes, flags,
- new SafeActivityOptions(opts), userId);
+ new SafeActivityOptions(opts, Binder.getCallingPid(), Binder.getCallingUid()),
+ userId);
WeakReference<PendingIntentRecord> ref;
ref = mIntentSenderRecords.get(key);
PendingIntentRecord rec = ref != null ? ref.get() : null;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 6857b6bcde15..3817ba1a28b9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -432,6 +432,14 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
}
}
+ /**
+ * get package name of the PendingIntent sender.
+ * @return package name of the PendingIntent sender.
+ */
+ public String getPackageName() {
+ return key.packageName;
+ }
+
@Deprecated
public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken,
IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,
@@ -525,9 +533,9 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
// Extract options before clearing calling identity
mergedOptions = key.options;
if (mergedOptions == null) {
- mergedOptions = new SafeActivityOptions(opts);
+ mergedOptions = new SafeActivityOptions(opts, callingPid, callingUid);
} else {
- mergedOptions.setCallerOptions(opts);
+ mergedOptions.setCallerOptions(opts, callingPid, callingUid);
}
if (mAllowlistDuration != null) {
@@ -621,7 +629,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
allIntents, allResolvedTypes, resultTo, mergedOptions, userId,
false /* validateIncomingUser */,
this /* originatingPendingIntent */,
- getBackgroundStartPrivilegesForActivitySender(allowlistToken));
+ getBackgroundStartPrivilegesForActivitySender(allowlistToken)
+ .allowsBackgroundActivityStarts());
} else {
res = controller.mAtmInternal.startActivityInPackage(uid, callingPid,
callingUid, key.packageName, key.featureId, finalIntent,
@@ -629,7 +638,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
mergedOptions, userId, null, "PendingIntentRecord",
false /* validateIncomingUser */,
this /* originatingPendingIntent */,
- getBackgroundStartPrivilegesForActivitySender(allowlistToken));
+ getBackgroundStartPrivilegesForActivitySender(allowlistToken)
+ .allowsBackgroundActivityStarts());
}
} catch (RuntimeException e) {
Slog.w(TAG, "Unable to send startActivity intent", e);
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index 5bd9c054dbf8..99bdd10ff71b 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -498,6 +498,15 @@ public final class PhantomProcessList {
app.killLocked("Caused by child process: " + msg, reasonCode, subReason, true);
}
+ @GuardedBy("mLock")
+ private SparseArray<PhantomProcessRecord> getPhantomProcessOfAppLocked(ProcessRecord app) {
+ int index = mAppPhantomProcessMap.indexOfKey(app.getPid());
+ if (index >= 0) {
+ return mAppPhantomProcessMap.valueAt(index);
+ }
+ return null;
+ }
+
/**
* Iterate all phantom process belonging to the given app, and invokve callback
* for each of them.
@@ -505,20 +514,36 @@ public final class PhantomProcessList {
void forEachPhantomProcessOfApp(final ProcessRecord app,
final Function<PhantomProcessRecord, Boolean> callback) {
synchronized (mLock) {
- int index = mAppPhantomProcessMap.indexOfKey(app.getPid());
- if (index >= 0) {
- final SparseArray<PhantomProcessRecord> array =
- mAppPhantomProcessMap.valueAt(index);
- for (int i = array.size() - 1; i >= 0; i--) {
- final PhantomProcessRecord r = array.valueAt(i);
- if (!callback.apply(r)) {
- break;
- }
+ final SparseArray<PhantomProcessRecord> array = getPhantomProcessOfAppLocked(app);
+ if (array == null) {
+ return;
+ }
+ for (int i = array.size() - 1; i >= 0; i--) {
+ final PhantomProcessRecord r = array.valueAt(i);
+ if (!callback.apply(r)) {
+ break;
}
}
}
}
+ /**
+ * Set process group of phantom process belonging to the given app.
+ */
+ void setProcessGroupForPhantomProcessOfApp(final ProcessRecord app, final int group) {
+ synchronized (mLock) {
+ lookForPhantomProcessesLocked(app);
+ final SparseArray<PhantomProcessRecord> array = getPhantomProcessOfAppLocked(app);
+ if (array == null) {
+ return;
+ }
+ for (int i = array.size() - 1; i >= 0; i--) {
+ final PhantomProcessRecord r = array.valueAt(i);
+ mService.mOomAdjuster.setProcessGroup(r.mPid, group, r.mProcessName);
+ }
+ }
+ }
+
@GuardedBy("tracker")
void updateProcessCpuStatesLocked(ProcessCpuTracker tracker) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 57a5e3f0c302..980c1f587677 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -18,8 +18,8 @@ package com.android.server.am;
import android.annotation.IntDef;
import android.annotation.UptimeMillisLong;
-import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityManagerInternal.FrozenProcessListener;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.util.Pair;
import android.util.TimeUtils;
@@ -338,7 +338,11 @@ final class ProcessCachedOptimizerRecord {
boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun,
@ShouldNotFreezeReason int reason, int adjSeq) {
if (dryRun) {
- return mFrozen && !shouldNotFreeze;
+ if (Flags.unfreezeBindPolicyFix()) {
+ return mShouldNotFreeze != shouldNotFreeze;
+ } else {
+ return mFrozen && !shouldNotFreeze;
+ }
}
if (Flags.traceUpdateAppFreezeStateLsp()) {
if (shouldNotFreeze) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index ba4b71cd7540..17fcbf47206f 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,7 @@ import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ProcessRecord.TAG;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -58,7 +58,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.modules.expresslog.Counter;
import com.android.server.ResourcePressureUtil;
import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.wm.WindowProcessController;
import java.io.File;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a0f2505a13e8..70febcd63455 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -22,6 +22,7 @@ import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
+import static android.content.pm.Flags.appCompatOption16kb;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -225,6 +226,7 @@ public final class ProcessList {
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
public static final int PREVIOUS_APP_ADJ = 700;
+ public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
@@ -2024,6 +2026,16 @@ public final class ProcessList {
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
+ if (appCompatOption16kb()) {
+ boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
+ if (is16KbDevice
+ && mService.mContext
+ .getPackageManager()
+ .isPageSizeCompatEnabled(app.info.packageName)) {
+ runtimeFlags |= Zygote.ENABLE_PAGE_SIZE_APP_COMPAT;
+ }
+ }
+
String invokeWith = null;
if (debuggableFlag) {
// Debuggable apps may include a wrapper script with their library directory.
@@ -3399,8 +3411,7 @@ public final class ProcessList {
// Check if we should mark the processrecord for first launch after force-stopping
if (wasStopped) {
boolean wasEverLaunched = false;
- if (android.app.Flags.useAppInfoNotLaunched()
- || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+ if (android.app.Flags.useAppInfoNotLaunched()) {
wasEverLaunched = !info.isNotLaunched();
} else {
try {
@@ -3421,8 +3432,7 @@ public final class ProcessList {
: STOPPED_STATE_FIRST_LAUNCH;
r.getWindowProcessController().setStoppedState(stoppedState);
} else {
- if (android.app.Flags.useAppInfoNotLaunched()
- || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+ if (android.app.Flags.useAppInfoNotLaunched()) {
// If it was launched before, then it must be a force-stop
r.setWasForceStopped(wasEverLaunched);
} else {
@@ -3439,12 +3449,12 @@ public final class ProcessList {
state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
state.setSetSchedGroup(ProcessList.SCHED_GROUP_DEFAULT);
r.setPersistent(true);
- state.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
+ mService.mProcessStateController.setMaxAdj(r, ProcessList.PERSISTENT_PROC_ADJ);
}
if (isolated && isolatedUid != 0) {
// Special case for startIsolatedProcess (internal only) - assume the process
// is required by the system server to prevent it being killed.
- state.setMaxAdj(ProcessList.PERSISTENT_SERVICE_ADJ);
+ mService.mProcessStateController.setMaxAdj(r, ProcessList.PERSISTENT_SERVICE_ADJ);
}
addProcessNameLocked(r);
return r;
@@ -3629,42 +3639,68 @@ public final class ProcessList {
}
@GuardedBy({"mService", "mProcLock"})
- private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index,
- int lruSeq, String what, Object obj, ProcessRecord srcApp) {
+ private int offerLruProcessInternalLSP(ProcessRecord app, long now, String what, Object obj,
+ ProcessRecord srcApp) {
app.setLastActivityTime(now);
if (app.hasActivitiesOrRecentTasks()) {
// Don't want to touch dependent processes that are hosting activities.
- return index;
+ return -1;
}
- int lrui = mLruProcesses.lastIndexOf(app);
+ final int lrui = mLruProcesses.lastIndexOf(app);
if (lrui < 0) {
Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ what + " " + obj + " from " + srcApp);
- return index;
}
+ return lrui;
+ }
- if (lrui >= index) {
- // Don't want to cause this to move dependent processes *back* in the
- // list as if they were less frequently used.
- return index;
- }
+ /**
+ * This method is called after the indices array is populated by the indices offered by
+ * {@link #offerLruProcessInternalLSP} to actually move the processes to the desired locations
+ * in the LRU list. Since the indices array is a SparseBooleanArray, the indices are sorted
+ * and this allows us to preserve the previous order of the processes relative to each other.
+ * Key of the indices array holds the current index of the process in the LRU list and the value
+ * is a boolean indicating whether the process is an activity process or not. Activity processes
+ * are moved to the nextActivityIndex and non-activity processes are moved to the nextIndex
+ * positions, which are provided by the caller.
+ *
+ * @param indices The indices of the processes to move.
+ * @param nextActivityIndex The next index to insert an activity process.
+ * @param nextIndex The next index to insert a non-activity process.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private void completeLruProcessInternalLSP(SparseBooleanArray indices, int nextActivityIndex,
+ int nextIndex) {
+ for (int i = indices.size() - 1; i >= 0; i--) {
+ final int lrui = indices.keyAt(i);
+ if (lrui < 0) {
+ // Rest of the indices are invalid, we can return early.
+ return;
+ }
+ final boolean isActivity = indices.valueAt(i);
+ int index = isActivity ? nextActivityIndex : nextIndex;
- if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) {
- // Don't want to touch dependent processes that are hosting activities.
- return index;
- }
+ if (lrui >= index) {
+ // Don't want to cause this to move dependent processes *back* in the
+ // list as if they were less frequently used.
+ continue;
+ }
- mLruProcesses.remove(lrui);
- if (index > 0) {
+ final ProcessRecord app = mLruProcesses.remove(lrui);
index--;
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ + " in LRU list: " + app);
+ mLruProcesses.add(index, app);
+ app.setLruSeq(mLruSeq);
+
+ if (isActivity) {
+ nextActivityIndex = index;
+ } else {
+ nextIndex = index;
+ }
}
- if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
- + " in LRU list: " + app);
- mLruProcesses.add(index, app);
- app.setLruSeq(lruSeq);
- return index;
}
/**
@@ -4058,6 +4094,15 @@ public final class ProcessList {
app.setLruSeq(mLruSeq);
+ // Key of the indices array holds the current index of the process in the LRU list and the
+ // value is a boolean indicating whether the process is an activity process or not.
+ // Activity processes will be moved to the nextActivityIndex and non-activity processes will
+ // be moved to the nextIndex positions when completeLruProcessInternalLSP is called.
+ // Since SparseBooleanArray's keys are sorted, we'll be able to keep the existing order of
+ // the processes relative to each other after the move.
+ final SparseBooleanArray indices = new SparseBooleanArray(psr.numberOfConnections()
+ + app.mProviders.numberOfProviderConnections());
+
// If the app is currently using a content provider or service,
// bump those processes as well.
for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
@@ -4069,16 +4114,12 @@ public final class ProcessList {
&& !cr.binding.service.app.isPersistent()) {
if (cr.binding.service.app.mServices.hasClientActivities()) {
if (nextActivityIndex >= 0) {
- nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextActivityIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), true);
}
} else {
- nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), false);
}
}
}
@@ -4086,10 +4127,11 @@ public final class ProcessList {
for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
- nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
- "provider reference", cpr, app);
+ indices.append(offerLruProcessInternalLSP(cpr.proc, now,
+ "provider reference", cpr, app), false);
}
}
+ completeLruProcessInternalLSP(indices, nextActivityIndex, nextIndex);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3e71d003f455..98f738c38d63 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -350,7 +351,8 @@ class ProcessRecord implements WindowProcessListener {
private String[] mIsolatedEntryPointArgs;
/**
- * Process is currently hosting a backup agent for backup or restore.
+ * Process is currently hosting a backup agent for backup or restore. Note that this is only set
+ * when the process is put into restricted backup mode.
*/
@GuardedBy("mService")
private boolean mInFullBackup;
@@ -1192,7 +1194,7 @@ class ProcessRecord implements WindowProcessListener {
setWaitingToKill(null);
mState.onCleanupApplicationRecordLSP();
- mServices.onCleanupApplicationRecordLocked();
+ mService.mProcessStateController.onCleanupApplicationRecord(mServices);
mReceivers.onCleanupApplicationRecordLocked();
mService.mOomAdjuster.onProcessEndLocked(this);
@@ -1638,7 +1640,7 @@ class ProcessRecord implements WindowProcessListener {
updateProcessInfo(false /* updateServiceConnectionActivities */,
true /* activityChange */, true /* updateOomAdj */);
setPendingUiClean(true);
- mState.setHasShownUi(true);
+ mService.mProcessStateController.setHasShownUi(this, true);
mState.forceProcessStateUpTo(topProcessState);
}
}
@@ -1657,7 +1659,10 @@ class ProcessRecord implements WindowProcessListener {
return;
}
synchronized (mService) {
- mState.setRunningRemoteAnimation(runningRemoteAnimation);
+ if (mService.mProcessStateController.setRunningRemoteAnimation(this,
+ runningRemoteAnimation)) {
+ mService.mProcessStateController.runUpdate(this, OOM_ADJ_REASON_UI_VISIBILITY);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 5cb8b954a2ba..14d3fbc22372 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -256,18 +256,24 @@ final class ProcessServiceRecord {
}
// Now we need to look at all short-FGS within the process and see if all of them are
// procstate-timed-out or not.
+ return !hasUndemotedShortForegroundService(nowUptime);
+ }
+
+ boolean hasUndemotedShortForegroundService(long nowUptime) {
for (int i = mServices.size() - 1; i >= 0; i--) {
final ServiceRecord sr = mServices.valueAt(i);
if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
continue;
}
if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
- return false;
+ // This short fgs has not timed out yet.
+ return true;
}
}
- return true;
+ return false;
}
+
int getReportedForegroundServiceTypes() {
return mRepFgServiceTypes;
}
@@ -387,6 +393,8 @@ final class ProcessServiceRecord {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+ } else if (Flags.addModifyRawOomAdjServiceLevel() && adj < ProcessList.SERVICE_ADJ) {
+ adj = ProcessList.SERVICE_ADJ;
} else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
adj = ProcessList.CACHED_APP_MIN_ADJ;
} else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
diff --git a/services/core/java/com/android/server/am/ProcessStateController.java b/services/core/java/com/android/server/am/ProcessStateController.java
new file mode 100644
index 000000000000..57899228e6ad
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessStateController.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.PowerManagerInternal;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+/**
+ * ProcessStateController is responsible for maintaining state that can affect the OomAdjuster
+ * computations of a process. Any state that can affect a process's importance must be set by
+ * only ProcessStateController.
+ */
+public class ProcessStateController {
+ public static String TAG = "ProcessStateController";
+
+ private final OomAdjuster mOomAdjuster;
+
+ private final GlobalState mGlobalState = new GlobalState();
+
+ private ProcessStateController(ActivityManagerService ams, ProcessList processList,
+ ActiveUids activeUids, ServiceThread handlerThread,
+ CachedAppOptimizer cachedAppOptimizer, OomAdjuster.Injector oomAdjInjector,
+ boolean useOomAdjusterModernImpl) {
+ mOomAdjuster = useOomAdjusterModernImpl
+ ? new OomAdjusterModernImpl(ams, processList, activeUids, handlerThread,
+ mGlobalState, cachedAppOptimizer, oomAdjInjector)
+ : new OomAdjuster(ams, processList, activeUids, handlerThread, mGlobalState,
+ cachedAppOptimizer, oomAdjInjector);
+ }
+
+ /**
+ * Get the instance of OomAdjuster that ProcessStateController is using.
+ * Must only be interacted with while holding the ActivityManagerService lock.
+ */
+ public OomAdjuster getOomAdjuster() {
+ return mOomAdjuster;
+ }
+
+ /**
+ * Add a process to evaluated the next time an update is run.
+ */
+ public void enqueueUpdateTarget(@NonNull ProcessRecord proc) {
+ mOomAdjuster.enqueueOomAdjTargetLocked(proc);
+ }
+
+ /**
+ * Remove a process that was added by {@link #enqueueUpdateTarget}.
+ */
+ public void removeUpdateTarget(@NonNull ProcessRecord proc, boolean procDied) {
+ mOomAdjuster.removeOomAdjTargetLocked(proc, procDied);
+ }
+
+ /**
+ * Trigger an update on a single process (and any processes that have been enqueued with
+ * {@link #enqueueUpdateTarget}).
+ */
+ public boolean runUpdate(@NonNull ProcessRecord proc,
+ @ActivityManagerInternal.OomAdjReason int oomAdjReason) {
+ return mOomAdjuster.updateOomAdjLocked(proc, oomAdjReason);
+ }
+
+ /**
+ * Trigger an update on all processes that have been enqueued with {@link #enqueueUpdateTarget}.
+ */
+ public void runPendingUpdate(@ActivityManagerInternal.OomAdjReason int oomAdjReason) {
+ mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
+ }
+
+ /**
+ * Trigger an update on all processes.
+ */
+ public void runFullUpdate(@ActivityManagerInternal.OomAdjReason int oomAdjReason) {
+ mOomAdjuster.updateOomAdjLocked(oomAdjReason);
+ }
+
+ /**
+ * Trigger an update on any processes that have been marked for follow up during a previous
+ * update.
+ */
+ public void runFollowUpUpdate() {
+ mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ }
+
+ private static class GlobalState implements OomAdjuster.GlobalState {
+ public boolean isAwake = true;
+ // TODO(b/369300367): Maintaining global state for backup processes is a bit convoluted.
+ // ideally the state gets migrated to ProcessStateRecord.
+ public final SparseArray<ProcessRecord> backupTargets = new SparseArray<>();
+ public boolean isLastMemoryLevelNormal = true;
+
+ public boolean isAwake() {
+ return isAwake;
+ }
+
+ public ProcessRecord getBackupTarget(@UserIdInt int userId) {
+ return backupTargets.get(userId);
+ }
+
+ public boolean isLastMemoryLevelNormal() {
+ return isLastMemoryLevelNormal;
+ }
+ }
+
+ /*************************** Global State Events ***************************/
+ /**
+ * Set which process state Top processes should get.
+ */
+ public void setTopProcessState(@ActivityManager.ProcessState int procState) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set whether to give Top processes the Top sched group.
+ */
+ public void setUseTopSchedGroupForTopProcess(boolean useTopSchedGroup) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set the Top process.
+ */
+ public void setTopApp(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is considered the Home process, if any.
+ */
+ public void setHomeProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is considered the Heavy Weight process, if any.
+ */
+ public void setHeavyWeightProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is showing UI while the screen is off, if any.
+ */
+ public void setVisibleDozeUiProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is considered the Previous process, if any.
+ */
+ public void setPreviousProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set what wakefulness state the screen is in.
+ */
+ public void setWakefulness(int wakefulness) {
+ mGlobalState.isAwake = (wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mOomAdjuster.onWakefulnessChanged(wakefulness);
+ }
+
+ /**
+ * Set for a given user what process is currently running a backup, if any.
+ */
+ public void setBackupTarget(@NonNull ProcessRecord proc, @UserIdInt int userId) {
+ mGlobalState.backupTargets.put(userId, proc);
+ }
+
+ /**
+ * No longer consider any process running a backup for a given user.
+ */
+ public void stopBackupTarget(@UserIdInt int userId) {
+ mGlobalState.backupTargets.delete(userId);
+ }
+
+ /**
+ * Set whether the last known memory level is normal.
+ */
+ public void setIsLastMemoryLevelNormal(boolean isMemoryNormal) {
+ mGlobalState.isLastMemoryLevelNormal = isMemoryNormal;
+ }
+
+ /***************************** UID State Events ****************************/
+ /**
+ * Set a UID as temp allowlisted.
+ */
+ public void setUidTempAllowlistStateLSP(int uid, boolean allowList) {
+ mOomAdjuster.setUidTempAllowlistStateLSP(uid, allowList);
+ }
+
+ /*********************** Process Miscellaneous Events **********************/
+ /**
+ * Set the maximum adj score a process can be assigned.
+ */
+ public void setMaxAdj(@NonNull ProcessRecord proc, int adj) {
+ proc.mState.setMaxAdj(adj);
+ }
+
+ /**
+ * Initialize a process that is being attached.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ public void setAttachingProcessStatesLSP(@NonNull ProcessRecord proc) {
+ mOomAdjuster.setAttachingProcessStatesLSP(proc);
+ }
+
+ /**
+ * Note whether a process is pending attach or not.
+ */
+ public void setPendingFinishAttach(@NonNull ProcessRecord proc, boolean pendingFinishAttach) {
+ proc.setPendingFinishAttach(pendingFinishAttach);
+ }
+
+ /**
+ * Set what sched group to grant a process due to running a broadcast.
+ * {@link ProcessList.SCHED_GROUP_UNDEFINED} means the process is not running a broadcast.
+ */
+ public void setBroadcastSchedGroup(@NonNull ProcessRecord proc, int schedGroup) {
+ // TODO(b/302575389): Migrate state pulled from BroadcastQueue to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /********************* Process Visibility State Events *********************/
+ /**
+ * Note whether a process has Top UI or not.
+ *
+ * @return true if the state changed, otherwise returns false.
+ */
+ public boolean setHasTopUi(@NonNull ProcessRecord proc, boolean hasTopUi) {
+ if (proc.mState.hasTopUi() == hasTopUi) return false;
+ if (DEBUG_OOM_ADJ) {
+ Slog.d(TAG, "Setting hasTopUi=" + hasTopUi + " for pid=" + proc.getPid());
+ }
+ proc.mState.setHasTopUi(hasTopUi);
+ return true;
+ }
+
+ /**
+ * Note whether a process is displaying Overlay UI or not.
+ *
+ * @return true if the state changed, otherwise returns false.
+ */
+ public boolean setHasOverlayUi(@NonNull ProcessRecord proc, boolean hasOverlayUi) {
+ if (proc.mState.hasOverlayUi() == hasOverlayUi) return false;
+ proc.mState.setHasOverlayUi(hasOverlayUi);
+ return true;
+ }
+
+
+ /**
+ * Note whether a process is running a remote animation.
+ *
+ * @return true if the state changed, otherwise returns false.
+ */
+ public boolean setRunningRemoteAnimation(@NonNull ProcessRecord proc,
+ boolean runningRemoteAnimation) {
+ if (proc.mState.isRunningRemoteAnimation() == runningRemoteAnimation) return false;
+ if (DEBUG_OOM_ADJ) {
+ Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+ + " for pid=" + proc.getPid());
+ }
+ proc.mState.setRunningRemoteAnimation(runningRemoteAnimation);
+ return true;
+ }
+
+ /**
+ * Note that the process is showing a toast.
+ */
+ public void setForcingToImportant(@NonNull ProcessRecord proc,
+ @Nullable Object forcingToImportant) {
+ if (proc.mState.getForcingToImportant() == forcingToImportant) return;
+ proc.mState.setForcingToImportant(forcingToImportant);
+ }
+
+ /**
+ * Note that the process has shown UI at some point in its life.
+ */
+ public void setHasShownUi(@NonNull ProcessRecord proc, boolean hasShownUi) {
+ // This arguably should be turned into an internal state of OomAdjuster.
+ if (proc.mState.hasShownUi() == hasShownUi) return;
+ proc.mState.setHasShownUi(hasShownUi);
+ }
+
+ /**
+ * Note whether the process has an activity or not.
+ */
+ public void setHasActivity(@NonNull ProcessRecord proc, boolean hasActivity) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ // Possibly not needed, maybe can use ActivityStateFlags.
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Note whether the process has a visibly activity or not.
+ */
+ public void setHasVisibleActivity(@NonNull ProcessRecord proc, boolean hasVisibleActivity) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ // maybe used ActivityStateFlags instead.
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set the Activity State Flags for a process.
+ */
+ public void setActivityStateFlags(@NonNull ProcessRecord proc, int flags) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /********************** Content Provider State Events **********************/
+ /**
+ * Note that a process is hosting a content provider.
+ */
+ public boolean addPublishedProvider(@NonNull ProcessRecord proc, String name,
+ ContentProviderRecord cpr) {
+ final ProcessProviderRecord providers = proc.mProviders;
+ if (providers.hasProvider(name)) return false;
+ providers.installProvider(name, cpr);
+ return true;
+ }
+
+ /**
+ * Remove a published content provider from a process.
+ */
+ public void removePublishedProvider(@NonNull ProcessRecord proc, String name) {
+ final ProcessProviderRecord providers = proc.mProviders;
+ providers.removeProvider(name);
+ }
+
+ /**
+ * Note that a content provider has an external client.
+ */
+ public void addExternalProviderClient(@NonNull ContentProviderRecord cpr,
+ IBinder externalProcessToken, int callingUid, String callingTag) {
+ cpr.addExternalProcessHandleLocked(externalProcessToken, callingUid, callingTag);
+ }
+
+ /**
+ * Remove an external client from a conetnt provider.
+ */
+ public boolean removeExternalProviderClient(@NonNull ContentProviderRecord cpr,
+ IBinder externalProcessToken) {
+ return cpr.removeExternalProcessHandleLocked(externalProcessToken);
+ }
+
+ /**
+ * Note the time a process is no longer hosting any content providers.
+ */
+ public void setLastProviderTime(@NonNull ProcessRecord proc, long uptimeMs) {
+ proc.mProviders.setLastProviderTime(uptimeMs);
+ }
+
+ /**
+ * Note that a process has connected to a content provider.
+ */
+ public void addProviderConnection(@NonNull ProcessRecord client,
+ ContentProviderConnection cpc) {
+ client.mProviders.addProviderConnection(cpc);
+ }
+
+ /**
+ * Note that a process is no longer connected to a content provider.
+ */
+ public void removeProviderConnection(@NonNull ProcessRecord client,
+ ContentProviderConnection cpc) {
+ client.mProviders.removeProviderConnection(cpc);
+ }
+
+ /*************************** Service State Events **************************/
+ /**
+ * Note that a process has started hosting a service.
+ */
+ public boolean startService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ return psr.startService(sr);
+ }
+
+ /**
+ * Note that a process has stopped hosting a service.
+ */
+ public boolean stopService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ return psr.stopService(sr);
+ }
+
+ /**
+ * Remove all services that the process is hosting.
+ */
+ public void stopAllServices(@NonNull ProcessServiceRecord psr) {
+ psr.stopAllServices();
+ }
+
+ /**
+ * Note that a process's service has started executing.
+ */
+ public void startExecutingService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ psr.startExecutingService(sr);
+ }
+
+ /**
+ * Note that a process's service has stopped executing.
+ */
+ public void stopExecutingService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ psr.stopExecutingService(sr);
+ }
+
+ /**
+ * Note all executing services a process has has stopped.
+ */
+ public void stopAllExecutingServices(@NonNull ProcessServiceRecord psr) {
+ psr.stopAllExecutingServices();
+ }
+
+ /**
+ * Note that process has bound to a service.
+ */
+ public void addConnection(@NonNull ProcessServiceRecord psr, ConnectionRecord cr) {
+ psr.addConnection(cr);
+ }
+
+ /**
+ * Note that process has unbound from a service.
+ */
+ public void removeConnection(@NonNull ProcessServiceRecord psr, ConnectionRecord cr) {
+ psr.removeConnection(cr);
+ }
+
+ /**
+ * Remove all bindings a process has to services.
+ */
+ public void removeAllConnections(@NonNull ProcessServiceRecord psr) {
+ psr.removeAllConnections();
+ psr.removeAllSdkSandboxConnections();
+ }
+
+ /**
+ * Note whether an executing service should be considered in the foreground or not.
+ */
+ public void setExecServicesFg(@NonNull ProcessServiceRecord psr, boolean execServicesFg) {
+ psr.setExecServicesFg(execServicesFg);
+ }
+
+ /**
+ * Note whether a service is in the foreground or not and what type of FGS, if so.
+ */
+ public void setHasForegroundServices(@NonNull ProcessServiceRecord psr,
+ boolean hasForegroundServices,
+ int fgServiceTypes, boolean hasTypeNoneFgs) {
+ psr.setHasForegroundServices(hasForegroundServices, fgServiceTypes, hasTypeNoneFgs);
+ }
+
+ /**
+ * Note whether a service has a client activity or not.
+ */
+ public void setHasClientActivities(@NonNull ProcessServiceRecord psr,
+ boolean hasClientActivities) {
+ psr.setHasClientActivities(hasClientActivities);
+ }
+
+ /**
+ * Note whether a service should be treated like an activity or not.
+ */
+ public void setTreatLikeActivity(@NonNull ProcessServiceRecord psr, boolean treatLikeActivity) {
+ psr.setTreatLikeActivity(treatLikeActivity);
+ }
+
+ /**
+ * Note whether a process has bound to a service with
+ * {@link android.content.Context.BIND_ABOVE_CLIENT} or not.
+ */
+ public void setHasAboveClient(@NonNull ProcessServiceRecord psr, boolean hasAboveClient) {
+ psr.setHasAboveClient(hasAboveClient);
+ }
+
+ /**
+ * Recompute whether a process has bound to a service with
+ * {@link android.content.Context.BIND_ABOVE_CLIENT} or not.
+ */
+ public void updateHasAboveClientLocked(@NonNull ProcessServiceRecord psr) {
+ psr.updateHasAboveClientLocked();
+ }
+
+ /**
+ * Cleanup a process's state.
+ */
+ public void onCleanupApplicationRecord(@NonNull ProcessServiceRecord psr) {
+ psr.onCleanupApplicationRecordLocked();
+ }
+
+ /**
+ * Set which process is hosting a service.
+ */
+ public void setHostProcess(@NonNull ServiceRecord sr, @Nullable ProcessRecord host) {
+ sr.app = host;
+ }
+
+ /**
+ * Note whether a service is a Foreground Service or not
+ */
+ public void setIsForegroundService(@NonNull ServiceRecord sr, boolean isFgs) {
+ sr.isForeground = isFgs;
+ }
+
+ /**
+ * Note the Foreground Service type of a service.
+ */
+ public void setForegroundServiceType(@NonNull ServiceRecord sr,
+ @ServiceInfo.ForegroundServiceType int fgsType) {
+ sr.foregroundServiceType = fgsType;
+ }
+
+ /**
+ * Note the start time of a short foreground service.
+ */
+ public void setShortFgsInfo(@NonNull ServiceRecord sr, long uptimeNow) {
+ sr.setShortFgsInfo(uptimeNow);
+ }
+
+ /**
+ * Note that a short foreground service has stopped.
+ */
+ public void clearShortFgsInfo(@NonNull ServiceRecord sr) {
+ sr.clearShortFgsInfo();
+ }
+
+ /**
+ * Note the last time a service was active.
+ */
+ public void setServiceLastActivityTime(@NonNull ServiceRecord sr, long lastActivityUpdateMs) {
+ sr.lastActivity = lastActivityUpdateMs;
+ }
+
+ /**
+ * Note that a service start was requested.
+ */
+ public void setStartRequested(@NonNull ServiceRecord sr, boolean startRequested) {
+ sr.startRequested = startRequested;
+ }
+
+ /**
+ * Note the last time the service was bound by a Top process with
+ * {@link android.content.Context.BIND_ALMOST_PERCEPTIBLE}
+ */
+ public void setLastTopAlmostPerceptibleBindRequest(@NonNull ServiceRecord sr,
+ long lastTopAlmostPerceptibleBindRequestUptimeMs) {
+ sr.lastTopAlmostPerceptibleBindRequestUptimeMs =
+ lastTopAlmostPerceptibleBindRequestUptimeMs;
+ }
+
+ /**
+ * Recompute whether a process has bound to a service with
+ * {@link android.content.Context.BIND_ALMOST_PERCEPTIBLE} or not.
+ */
+ public void updateHasTopStartedAlmostPerceptibleServices(@NonNull ProcessServiceRecord psr) {
+ psr.updateHasTopStartedAlmostPerceptibleServices();
+ }
+
+ /**
+ * Builder for ProcessStateController.
+ */
+ public static class Builder {
+ private final ActivityManagerService mAms;
+ private final ProcessList mProcessList;
+ private final ActiveUids mActiveUids;
+
+ private ServiceThread mHandlerThread = null;
+ private CachedAppOptimizer mCachedAppOptimizer = null;
+ private OomAdjuster.Injector mOomAdjInjector = null;
+ private boolean mUseOomAdjusterModernImpl = false;
+
+ public Builder(ActivityManagerService ams, ProcessList processList, ActiveUids activeUids) {
+ mAms = ams;
+ mProcessList = processList;
+ mActiveUids = activeUids;
+ }
+
+ /**
+ * Build the ProcessStateController object.
+ */
+ public ProcessStateController build() {
+ if (mHandlerThread == null) {
+ mHandlerThread = OomAdjuster.createAdjusterThread();
+ }
+ if (mCachedAppOptimizer == null) {
+ mCachedAppOptimizer = new CachedAppOptimizer(mAms);
+ }
+ if (mOomAdjInjector == null) {
+ mOomAdjInjector = new OomAdjuster.Injector();
+ }
+ return new ProcessStateController(mAms, mProcessList, mActiveUids, mHandlerThread,
+ mCachedAppOptimizer, mOomAdjInjector, mUseOomAdjusterModernImpl);
+ }
+
+ /**
+ * For Testing Purposes. Set what thread OomAdjuster will offload tasks on to.
+ */
+ @VisibleForTesting
+ public Builder setHandlerThread(ServiceThread handlerThread) {
+ mHandlerThread = handlerThread;
+ return this;
+ }
+
+ /**
+ * For Testing Purposes. Set the CachedAppOptimzer used by OomAdjuster.
+ */
+ @VisibleForTesting
+ public Builder setCachedAppOptimizer(CachedAppOptimizer cachedAppOptimizer) {
+ mCachedAppOptimizer = cachedAppOptimizer;
+ return this;
+ }
+
+ /**
+ * For Testing Purposes. Set an injector for OomAdjuster.
+ */
+ @VisibleForTesting
+ public Builder setOomAdjusterInjector(OomAdjuster.Injector injector) {
+ mOomAdjInjector = injector;
+ return this;
+ }
+
+ /**
+ * Set which implementation of OomAdjuster to use.
+ */
+ public Builder useModernOomAdjuster(boolean use) {
+ mUseOomAdjusterModernImpl = use;
+ return this;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index bc990d9c5ef9..b0f808b39053 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -19,14 +19,11 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_ACTIVITY;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_STARTED_SERVICE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
-import static com.android.server.am.ProcessRecord.TAG;
import static com.android.server.wm.WindowProcessController.ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED;
import static com.android.server.wm.WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING;
import static com.android.server.wm.WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING;
@@ -38,7 +35,6 @@ import android.app.ActivityManager;
import android.content.ComponentName;
import android.os.SystemClock;
import android.os.Trace;
-import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.annotations.CompositeRWLock;
@@ -790,15 +786,7 @@ final class ProcessStateRecord {
@GuardedBy("mService")
void setRunningRemoteAnimation(boolean runningRemoteAnimation) {
- if (mRunningRemoteAnimation == runningRemoteAnimation) {
- return;
- }
mRunningRemoteAnimation = runningRemoteAnimation;
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
- + " for pid=" + mApp.getPid());
- }
- mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index b9cdf27a7415..92d33c9eae56 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1248,7 +1248,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
app.mServices.updateBoundClientUids();
app.mServices.updateHostingComonentTypeForBindingsLocked();
}
- app = proc;
+ ams.mProcessStateController.setHostProcess(this, proc);
updateProcessStateOnRequest();
if (pendingConnectionGroup > 0 && proc != null) {
final ProcessServiceRecord psr = proc.mServices;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index ccc0a25633e5..2905403931ab 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -40,6 +40,8 @@ import android.aconfigd.Aconfigd.StorageRequestMessages;
import android.aconfigd.Aconfigd.StorageReturnMessage;
import android.aconfigd.Aconfigd.StorageReturnMessages;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
import java.io.DataInputStream;
@@ -139,18 +141,30 @@ public class SettingsToPropertiesMapper {
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "tv_os",
+ "aaos_carframework_triage",
+ "aaos_performance_triage",
+ "aaos_input_triage",
+ "aaos_user_triage",
+ "aaos_window_triage",
+ "aaos_audio_triage",
+ "aaos_power_triage",
+ "aaos_storage_triage",
+ "aaos_sdv",
+ "aaos_vac_triage",
"accessibility",
"android_core_networking",
"android_health_services",
"android_sdk",
- "android_stylus",
"aoc",
"app_widgets",
"arc_next",
"art_mainline",
"art_performance",
"attack_tools",
+ "automotive_cast",
"avic",
+ "desktop_firmware",
"biometrics",
"biometrics_framework",
"biometrics_integration",
@@ -174,6 +188,8 @@ public class SettingsToPropertiesMapper {
"core_libraries",
"crumpet",
"dck_framework",
+ "desktop_hwsec",
+ "desktop_stats",
"devoptions_settings",
"game",
"gpu",
@@ -189,6 +205,7 @@ public class SettingsToPropertiesMapper {
"make_pixel_haptics",
"media_audio",
"media_drm",
+ "media_projection",
"media_reliability",
"media_solutions",
"media_tv",
@@ -201,8 +218,12 @@ public class SettingsToPropertiesMapper {
"pixel_bluetooth",
"pixel_connectivity_gps",
"pixel_continuity",
+ "pixel_display",
+ "pixel_perf",
"pixel_sensors",
+ "pixel_state_server",
"pixel_system_sw_video",
+ "pixel_video_sw",
"pixel_watch",
"platform_compat",
"platform_security",
@@ -212,6 +233,8 @@ public class SettingsToPropertiesMapper {
"preload_safety",
"printing",
"privacy_infra_policy",
+ "psap_ai",
+ "ravenwood",
"resource_manager",
"responsible_apis",
"rust",
@@ -228,6 +251,7 @@ public class SettingsToPropertiesMapper {
"text",
"threadnetwork",
"treble",
+ "tv_os_media",
"tv_system_ui",
"usb",
"vibrator",
@@ -244,8 +268,11 @@ public class SettingsToPropertiesMapper {
"wear_system_health",
"wear_systems",
"wear_sysui",
+ "wear_system_managed_surfaces",
+ "wear_watchfaces",
"window_surfaces",
"windowing_frontend",
+ "xr",
};
public static final String NAMESPACE_REBOOT_STAGING = "staged";
@@ -516,14 +543,18 @@ public class SettingsToPropertiesMapper {
static void writeFlagOverrideRequest(
ProtoOutputStream proto, String packageName, String flagName, String flagValue,
boolean isLocal) {
+ int localOverrideTag = supportImmediateLocalOverrides()
+ ? StorageRequestMessage.LOCAL_IMMEDIATE
+ : StorageRequestMessage.LOCAL_ON_REBOOT;
+
long msgsToken = proto.start(StorageRequestMessages.MSGS);
long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
proto.write(StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, isLocal
- ? StorageRequestMessage.LOCAL_IMMEDIATE
- : StorageRequestMessage.SERVER_ON_REBOOT);
+ ? localOverrideTag
+ : StorageRequestMessage.SERVER_ON_REBOOT);
proto.end(msgToken);
proto.end(msgsToken);
}
@@ -534,14 +565,20 @@ public class SettingsToPropertiesMapper {
* @param proto
* @param packageName the package of the flag
* @param flagName the name of the flag
+ * @param immediate if true, clear immediately; otherwise, clear on next reboot
*/
- static void writeFlagOverrideRemovalRequest(
- ProtoOutputStream proto, String packageName, String flagName) {
+ @VisibleForTesting
+ public static void writeFlagOverrideRemovalRequest(
+ ProtoOutputStream proto, String packageName, String flagName, boolean immediate) {
long msgsToken = proto.start(StorageRequestMessages.MSGS);
long msgToken = proto.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME, packageName);
proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flagName);
proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false);
+ proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_OVERRIDE_TYPE,
+ immediate
+ ? StorageRequestMessage.REMOVE_LOCAL_IMMEDIATE
+ : StorageRequestMessage.REMOVE_LOCAL_ON_REBOOT);
proto.end(msgToken);
proto.end(msgsToken);
}
@@ -586,7 +623,8 @@ public class SettingsToPropertiesMapper {
* apply flag local override in aconfig new storage
* @param props
*/
- static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+ @VisibleForTesting
+ public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
int num_requests = 0;
ProtoOutputStream requests = new ProtoOutputStream();
for (String flagName : props.getKeyset()) {
@@ -618,7 +656,11 @@ public class SettingsToPropertiesMapper {
String realFlagName = fullFlagName.substring(idx+1);
if (Flags.syncLocalOverridesRemovalNewStorage() && flagValue == null) {
- writeFlagOverrideRemovalRequest(requests, packageName, realFlagName);
+ if (supportClearLocalOverridesImmediately()) {
+ writeFlagOverrideRemovalRequest(requests, packageName, realFlagName, true);
+ } else {
+ writeFlagOverrideRemovalRequest(requests, packageName, realFlagName, false);
+ }
} else {
writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 262c76e4a4d7..c31b9ef60bd2 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1920,180 +1920,190 @@ class UserController implements Handler.Callback {
return false;
}
- boolean needStart = false;
- boolean updateUmState = false;
- UserState uss;
+ mHandler.post(() -> startUserInternalOnHandler(userId, oldUserId, userStartMode,
+ unlockListener, callingUid, callingPid));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
- // If the user we are switching to is not currently started, then
- // we need to start it now.
- t.traceBegin("updateStartedUserArrayStarting");
- synchronized (mLock) {
- uss = mStartedUsers.get(userId);
- if (uss == null) {
- uss = new UserState(UserHandle.of(userId));
- uss.mUnlockProgress.addListener(new UserProgressListener());
- mStartedUsers.put(userId, uss);
- updateStartedUserArrayLU();
- needStart = true;
- updateUmState = true;
- } else if (uss.state == UserState.STATE_SHUTDOWN
- || mDoNotAbortShutdownUserIds.contains(userId)) {
- Slogf.i(TAG, "User #" + userId
- + " is shutting down - will start after full shutdown");
- mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
- unlockListener));
- t.traceEnd(); // updateStartedUserArrayStarting
- return true;
- }
- }
+ return true;
+ }
- // No matter what, the fact that we're requested to start the user (even if it is
- // already running) puts it towards the end of the mUserLru list.
- addUserToUserLru(userId);
- if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
- mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
- Integer.valueOf(userId));
- }
+ private void startUserInternalOnHandler(int userId, int oldUserId, int userStartMode,
+ IProgressListener unlockListener, int callingUid, int callingPid) {
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
+ final UserInfo userInfo = getUserInfo(userId);
- if (unlockListener != null) {
- uss.mUnlockProgress.addListener(unlockListener);
- }
- t.traceEnd(); // updateStartedUserArrayStarting
+ boolean needStart = false;
+ boolean updateUmState = false;
+ UserState uss;
- if (updateUmState) {
- t.traceBegin("setUserState");
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- t.traceEnd();
+ // If the user we are switching to is not currently started, then
+ // we need to start it now.
+ t.traceBegin("updateStartedUserArrayStarting");
+ synchronized (mLock) {
+ uss = mStartedUsers.get(userId);
+ if (uss == null) {
+ uss = new UserState(UserHandle.of(userId));
+ uss.mUnlockProgress.addListener(new UserProgressListener());
+ mStartedUsers.put(userId, uss);
+ updateStartedUserArrayLU();
+ needStart = true;
+ updateUmState = true;
+ } else if (uss.state == UserState.STATE_SHUTDOWN
+ || mDoNotAbortShutdownUserIds.contains(userId)) {
+ Slogf.i(TAG, "User #" + userId
+ + " is shutting down - will start after full shutdown");
+ mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
+ unlockListener));
+ t.traceEnd(); // updateStartedUserArrayStarting
+ return;
}
- t.traceBegin("updateConfigurationAndProfileIds");
- if (foreground) {
- // Make sure the old user is no longer considering the display to be on.
- mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
- boolean userSwitchUiEnabled;
- synchronized (mLock) {
- mCurrentUserId = userId;
- ActivityManager.invalidateGetCurrentUserIdCache();
- userSwitchUiEnabled = mUserSwitchUiEnabled;
- }
- mInjector.updateUserConfiguration();
- // NOTE: updateProfileRelatedCaches() is called on both if and else parts, ideally
- // it should be moved outside, but for now it's not as there are many calls to
- // external components here afterwards
- updateProfileRelatedCaches();
- dispatchOnBeforeUserSwitching(userId);
- mInjector.getWindowManager().setCurrentUser(userId);
- mInjector.reportCurWakefulnessUsageEvent();
- // Once the internal notion of the active user has switched, we lock the device
- // with the option to show the user switcher on the keyguard.
- if (userSwitchUiEnabled) {
- mInjector.getWindowManager().setSwitchingUser(true);
- // Only lock if the user has a secure keyguard PIN/Pattern/Pwd
- if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
- // Make sure the device is locked before moving on with the user switch
- mInjector.lockDeviceNowAndWaitForKeyguardShown();
- }
- }
+ }
- } else {
- updateProfileRelatedCaches();
- // We are starting a non-foreground user. They have already been added to the end
- // of mUserLru, so we need to ensure that the foreground user isn't displaced.
- addUserToUserLru(mCurrentUserId);
- }
- if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) {
- scheduleStopOfBackgroundUser(userId);
- }
- t.traceEnd();
+ // No matter what, the fact that we're requested to start the user (even if it is
+ // already running) puts it towards the end of the mUserLru list.
+ addUserToUserLru(userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
- // Make sure user is in the started state. If it is currently
- // stopping, we need to knock that off.
- if (uss.state == UserState.STATE_STOPPING) {
- t.traceBegin("updateStateStopping");
- // If we are stopping, we haven't sent ACTION_SHUTDOWN,
- // so we can just fairly silently bring the user back from
- // the almost-dead.
- uss.setState(uss.lastState);
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- synchronized (mLock) {
- updateStartedUserArrayLU();
- }
- needStart = true;
- t.traceEnd();
- } else if (uss.state == UserState.STATE_SHUTDOWN) {
- t.traceBegin("updateStateShutdown");
- // This means ACTION_SHUTDOWN has been sent, so we will
- // need to treat this as a new boot of the user.
- uss.setState(UserState.STATE_BOOTING);
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- synchronized (mLock) {
- updateStartedUserArrayLU();
+ if (unlockListener != null) {
+ uss.mUnlockProgress.addListener(unlockListener);
+ }
+ t.traceEnd(); // updateStartedUserArrayStarting
+
+ if (updateUmState) {
+ t.traceBegin("setUserState");
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ t.traceEnd();
+ }
+ t.traceBegin("updateConfigurationAndProfileIds");
+ if (foreground) {
+ // Make sure the old user is no longer considering the display to be on.
+ mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+ boolean userSwitchUiEnabled;
+ synchronized (mLock) {
+ mCurrentUserId = userId;
+ ActivityManager.invalidateGetCurrentUserIdCache();
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
+ }
+ mInjector.updateUserConfiguration();
+ // NOTE: updateProfileRelatedCaches() is called on both if and else parts, ideally
+ // it should be moved outside, but for now it's not as there are many calls to
+ // external components here afterwards
+ updateProfileRelatedCaches();
+ dispatchOnBeforeUserSwitching(userId);
+ mInjector.getWindowManager().setCurrentUser(userId);
+ mInjector.reportCurWakefulnessUsageEvent();
+ // Once the internal notion of the active user has switched, we lock the device
+ // with the option to show the user switcher on the keyguard.
+ if (userSwitchUiEnabled) {
+ mInjector.getWindowManager().setSwitchingUser(true);
+ // Only lock if the user has a secure keyguard PIN/Pattern/Pwd
+ if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
+ // Make sure the device is locked before moving on with the user switch
+ mInjector.lockDeviceNowAndWaitForKeyguardShown();
}
- needStart = true;
- t.traceEnd();
}
- if (uss.state == UserState.STATE_BOOTING) {
- t.traceBegin("updateStateBooting");
- // Give user manager a chance to propagate user restrictions
- // to other services and prepare app storage
- mInjector.getUserManager().onBeforeStartUser(userId);
+ } else {
+ updateProfileRelatedCaches();
+ // We are starting a non-foreground user. They have already been added to the end
+ // of mUserLru, so we need to ensure that the foreground user isn't displaced.
+ addUserToUserLru(mCurrentUserId);
+ }
+ if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) {
+ scheduleStopOfBackgroundUser(userId);
+ }
+ t.traceEnd();
- // Booting up a new user, need to tell system services about it.
- // Note that this is on the same handler as scheduling of broadcasts,
- // which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
- t.traceEnd();
+ // Make sure user is in the started state. If it is currently
+ // stopping, we need to knock that off.
+ if (uss.state == UserState.STATE_STOPPING) {
+ t.traceBegin("updateStateStopping");
+ // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+ // so we can just fairly silently bring the user back from
+ // the almost-dead.
+ uss.setState(uss.lastState);
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ synchronized (mLock) {
+ updateStartedUserArrayLU();
}
-
- t.traceBegin("sendMessages");
- if (foreground) {
- mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
- mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
- oldUserId, userId, uss));
- mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
- oldUserId, userId, uss), getUserSwitchTimeoutMs());
+ needStart = true;
+ t.traceEnd();
+ } else if (uss.state == UserState.STATE_SHUTDOWN) {
+ t.traceBegin("updateStateShutdown");
+ // This means ACTION_SHUTDOWN has been sent, so we will
+ // need to treat this as a new boot of the user.
+ uss.setState(UserState.STATE_BOOTING);
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ synchronized (mLock) {
+ updateStartedUserArrayLU();
}
+ needStart = true;
+ t.traceEnd();
+ }
- if (userInfo.preCreated) {
- needStart = false;
- }
+ if (uss.state == UserState.STATE_BOOTING) {
+ t.traceBegin("updateStateBooting");
+ // Give user manager a chance to propagate user restrictions
+ // to other services and prepare app storage
+ mInjector.getUserManager().onBeforeStartUser(userId);
- // In most cases, broadcast for the system user starting/started is sent by
- // ActivityManagerService#systemReady(). However on some HSUM devices (e.g. tablets)
- // the user switches from the system user to a secondary user while running
- // ActivityManagerService#systemReady(), thus broadcast is not sent for the system user.
- // Therefore we send the broadcast for the system user here as well in HSUM.
- // TODO(b/266158156): Improve/refactor the way broadcasts are sent for the system user
- // in HSUM. Ideally it'd be best to have one single place that sends this notification.
- final boolean isSystemUserInHeadlessMode = (userId == UserHandle.USER_SYSTEM)
- && mInjector.isHeadlessSystemUserMode();
- if (needStart || isSystemUserInHeadlessMode) {
- sendUserStartedBroadcast(userId, callingUid, callingPid);
- }
+ // Booting up a new user, need to tell system services about it.
+ // Note that this is on the same handler as scheduling of broadcasts,
+ // which is important because it needs to go first.
+ mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
t.traceEnd();
+ }
- if (foreground) {
- t.traceBegin("moveUserToForeground");
- moveUserToForeground(uss, userId);
- t.traceEnd();
- } else {
- t.traceBegin("finishUserBoot");
- finishUserBoot(uss);
- t.traceEnd();
- }
+ t.traceBegin("sendMessages");
+ if (foreground) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
+ mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+ mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+ oldUserId, userId, uss));
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+ oldUserId, userId, uss), getUserSwitchTimeoutMs());
+ }
- if (needStart || isSystemUserInHeadlessMode) {
- t.traceBegin("sendRestartBroadcast");
- sendUserStartingBroadcast(userId, callingUid, callingPid);
- t.traceEnd();
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ if (userInfo.preCreated) {
+ needStart = false;
}
- return true;
+ // In most cases, broadcast for the system user starting/started is sent by
+ // ActivityManagerService#systemReady(). However on some HSUM devices (e.g. tablets)
+ // the user switches from the system user to a secondary user while running
+ // ActivityManagerService#systemReady(), thus broadcast is not sent for the system user.
+ // Therefore we send the broadcast for the system user here as well in HSUM.
+ // TODO(b/266158156): Improve/refactor the way broadcasts are sent for the system user
+ // in HSUM. Ideally it'd be best to have one single place that sends this notification.
+ final boolean isSystemUserInHeadlessMode = (userId == UserHandle.USER_SYSTEM)
+ && mInjector.isHeadlessSystemUserMode();
+ if (needStart || isSystemUserInHeadlessMode) {
+ sendUserStartedBroadcast(userId, callingUid, callingPid);
+ }
+ t.traceEnd();
+
+ if (foreground) {
+ t.traceBegin("moveUserToForeground");
+ moveUserToForeground(uss, userId);
+ t.traceEnd();
+ } else {
+ t.traceBegin("finishUserBoot");
+ finishUserBoot(uss);
+ t.traceEnd();
+ }
+
+ if (needStart || isSystemUserInHeadlessMode) {
+ t.traceBegin("sendRestartBroadcast");
+ sendUserStartingBroadcast(userId, callingUid, callingPid);
+ t.traceEnd();
+ }
}
/**
@@ -2373,7 +2383,7 @@ class UserController implements Handler.Callback {
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
if (hasRestriction || isStopUserOnSwitchEnabled()) {
Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
- stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ !hasRestriction, null, null);
return;
}
}
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
new file mode 100644
index 000000000000..7f169db7dcec
--- /dev/null
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -0,0 +1,18 @@
+package: "com.android.server.am"
+container: "system"
+
+flag {
+ name: "restrict_priority_values"
+ namespace: "backstage_power"
+ description: "Restrict priority values defined by non-system apps"
+ is_fixed_read_only: true
+ bug: "369487976"
+}
+
+flag {
+ name: "limit_priority_scope"
+ namespace: "backstage_power"
+ description: "Limit the scope of receiver priorities to within a process"
+ is_fixed_read_only: true
+ bug: "369487976"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 3334393a1618..89e4a8d82676 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -90,14 +90,6 @@ flag {
flag {
namespace: "backstage_power"
- name: "use_permission_manager_for_broadcast_delivery_check"
- description: "Use PermissionManager API for broadcast delivery permission checks."
- bug: "315468967"
- is_fixed_read_only: true
-}
-
-flag {
- namespace: "backstage_power"
name: "trace_receiver_registration"
description: "Add tracing for broadcast receiver registration and un-registration"
bug: "336385821"
@@ -194,4 +186,105 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "logcat_longer_timeout"
+ namespace: "stability"
+ description: "Wait longer during the logcat gathering operation"
+ bug: "292533246"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "defer_display_events_when_frozen"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Defer submitting display events to frozen processes."
+ bug: "326315985"
+}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "system_performance"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
+
+flag {
+ name: "push_global_state_to_oomadjuster"
+ namespace: "backstage_power"
+ description: "Migrate OomAdjuster pulled device state to a push model"
+ bug: "302575389"
+}
+
+flag {
+ name: "oomadjuster_cached_app_tiers"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Assign cached oom_score_adj in tiers."
+ bug: "369893532"
+}
+
+flag {
+ name: "unfreeze_bind_policy_fix"
+ namespace: "backstage_power"
+ description: "Make sure shouldNotFreeze state change correctly triggers updates."
+ bug: "375691778"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "oomadjuster_prev_laddering"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Add +X to the prev scores according to their positions in the process LRU list"
+ bug: "359912586"
+}
+
+flag {
+ name: "fix_apply_oomadj_order"
+ namespace: "backstage_power"
+ is_fixed_read_only: true
+ description: "Fix the iteration direction of process LRU list when applying oom adj"
+ bug: "378580264"
+}
+
+flag {
+ name: "phantom_processes_fix"
+ namespace: "backstage_power"
+ description: "Make sure setProcessGroupForPhantomProcessOfApp deals with phantom processes properly"
+ bug: "375058190"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "use_cpu_time_capability"
+ namespace: "backstage_power"
+ description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
+ bug: "370817323"
+}
+
+flag {
+ name: "add_modify_raw_oom_adj_service_level"
+ namespace: "backstage_power"
+ description: "Add a SERVICE_ADJ level to the modifyRawOomAdj method"
+ bug: "374810368"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "app_start_info_isolated_process"
+ namespace: "system_performance"
+ description: "Adjust handling of isolated process records to be discarded."
+ bug: "374032823"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index ae93991d3945..0855815b67a9 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -65,27 +65,31 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
@Override
public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+ boolean changed;
if (restricted) {
if (!mGlobalRestrictions.containsKey(clientToken)) {
mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
}
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
Objects.requireNonNull(restrictedCodes);
- boolean changed = !restrictedCodes.get(code);
+ changed = !restrictedCodes.get(code);
restrictedCodes.put(code, true);
- return changed;
} else {
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
if (restrictedCodes == null) {
return false;
}
- boolean changed = restrictedCodes.get(code);
+ changed = restrictedCodes.get(code);
restrictedCodes.delete(code);
if (restrictedCodes.size() == 0) {
mGlobalRestrictions.remove(clientToken);
}
- return changed;
}
+
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
+ return changed;
}
@Override
@@ -104,7 +108,11 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
@Override
public boolean clearGlobalRestrictions(Object clientToken) {
- return mGlobalRestrictions.remove(clientToken) != null;
+ boolean changed = mGlobalRestrictions.remove(clientToken) != null;
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
+ return changed;
}
@RequiresPermission(anyOf = {
@@ -122,6 +130,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
changed |= putUserRestrictionExclusions(clientToken, userIds[i],
excludedPackageTags);
}
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
return changed;
}
@@ -191,6 +202,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
changed |= mUserRestrictions.remove(clientToken) != null;
changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
notifyAllUserRestrictions(allUserRestrictedCodes);
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
return changed;
}
@@ -244,6 +258,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
}
}
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
return changed;
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2fb58f00df6e..144799383fbb 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -70,8 +70,15 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.os.Flags.binderFrozenStateChangeCallback;
+import static android.permission.flags.Flags.checkOpValidatePackage;
import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
+import static android.permission.flags.Flags.useFrozenAwareRemoteCallbackList;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
@@ -160,6 +167,7 @@ import com.android.internal.os.Clock;
import com.android.internal.pm.pkg.component.ParsedAttribution;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -176,6 +184,7 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.policy.AppOpsPolicy;
+import com.android.server.selinux.RateLimiter;
import dalvik.annotation.optimization.NeverCompile;
@@ -195,6 +204,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
+import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -345,6 +355,10 @@ public class AppOpsService extends IAppOpsService.Stub {
@GuardedBy("this")
private boolean mUidStatesInitialized;
+ // A rate limiter to prevent excessive Atom pushing. Used by noteOperation.
+ private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
+ private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW);
+
volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
/*
@@ -984,6 +998,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onUidModeChanged(int uid, int code, int mode,
String persistentDeviceId) {
+ AppOpsManager.invalidateAppOpModeCache();
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
code, uid, false, persistentDeviceId));
@@ -992,6 +1007,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onPackageModeChanged(String packageName, int userId, int code,
int mode) {
+ AppOpsManager.invalidateAppOpModeCache();
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForPkg, AppOpsService.this,
packageName, code, mode, userId));
@@ -1018,6 +1034,11 @@ public class AppOpsService extends IAppOpsService.Stub {
// To migrate storageFile to recentAccessesFile, these reads must be called in this order.
readRecentAccesses();
mAppOpsCheckingService.readState();
+ // The system property used by the cache is created the first time it is written, that only
+ // happens inside invalidateCache(). Until the service calls invalidateCache() the property
+ // will not exist and the nonce will be UNSET.
+ AppOpsManager.invalidateAppOpModeCache();
+ AppOpsManager.disableAppOpModeCache();
}
public void publish() {
@@ -2814,6 +2835,13 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public int checkOperationRaw(int code, int uid, String packageName,
@Nullable String attributionTag) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
Context.DEVICE_ID_DEFAULT, true /*raw*/);
}
@@ -2821,41 +2849,79 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, int virtualDeviceId) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
virtualDeviceId, true /*raw*/);
}
@Override
public int checkOperation(int code, int uid, String packageName) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
Context.DEVICE_ID_DEFAULT, false /*raw*/);
}
@Override
- public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
- return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
+ public int checkOperationForDevice(int code, int uid, String packageName,
+ @Nullable String attributionTag, int virtualDeviceId) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
+ return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
virtualDeviceId, false /*raw*/);
}
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, int virtualDeviceId, boolean raw) {
- verifyIncomingOp(code);
- if (!isValidVirtualDeviceId(virtualDeviceId)) {
- Slog.w(TAG,
- "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
- + " is invalid");
- return AppOpsManager.MODE_IGNORED;
- }
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return AppOpsManager.opToDefaultMode(code);
+ String resolvedPackageName;
+ if (!shouldUseNewCheckOp()) {
+ verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG, "checkOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + virtualDeviceId + " is invalid");
+ return AppOpsManager.MODE_IGNORED;
+ }
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ } else {
+ // Note, this flag changes the behavior in this case: invalid packages now don't
+ // succeed checkOp
+ resolvedPackageName = validateOpRequest(code, uid, packageName,
+ virtualDeviceId, false, "checkOperation");
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
}
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
+ if (Flags.appopModeCachingEnabled()) {
+ return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId,
+ raw, true);
+ } else {
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId, raw);
}
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- virtualDeviceId, raw);
}
/**
@@ -2921,6 +2987,54 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
+ /**
+ * This method unifies mode checking logic between checkOperationUnchecked and
+ * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out.
+ *
+ * @param isCheckOp This param is only used in user's op restriction. When checking if a package
+ * can bypass user's restriction we should account for attributionTag as well.
+ * But existing checkOp APIs don't accept attributionTag so we added a hack to
+ * skip attributionTag check for checkOp. After we add an overload of checkOp
+ * that accepts attributionTag we should remove this param.
+ */
+ private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+ } catch (SecurityException e) {
+ logVerifyAndGetBypassFailure(uid, e, "getAppOpMode");
+ return MODE_IGNORED;
+ }
+
+ if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+ return MODE_IGNORED;
+ }
+
+ synchronized (this) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, isCheckOp)) {
+ return MODE_IGNORED;
+ }
+ if (isOpAllowedForUid(uid)) {
+ return MODE_ALLOWED;
+ }
+
+ int switchCode = AppOpsManager.opToSwitch(code);
+ int rawUidMode = mAppOpsCheckingService.getUidMode(uid,
+ getPersistentId(virtualDeviceId), switchCode);
+
+ if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) {
+ return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode);
+ }
+
+ int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode,
+ UserHandle.getUserId(uid));
+ return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode);
+ }
+ }
+
+
@Override
public int checkAudioOperation(int code, int usage, int uid, String packageName) {
return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName);
@@ -3013,6 +3127,13 @@ public class AppOpsService extends IAppOpsService.Stub {
public SyncNotedAppOp noteProxyOperationWithState(int code,
AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage, boolean skipProxyOperation) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, attributionSourceState.uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION,
+ attributionSourceState.attributionTag != null);
+ }
AttributionSource attributionSource = new AttributionSource(attributionSourceState);
return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
@@ -3094,6 +3215,15 @@ public class AppOpsService extends IAppOpsService.Stub {
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ if (mRateLimiter.tryAcquire()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
+ attributionTag != null);
+ }
+ }
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
shouldCollectMessage);
@@ -3112,25 +3242,37 @@ public class AppOpsService extends IAppOpsService.Stub {
@Nullable String attributionTag, int virtualDeviceId,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isValidVirtualDeviceId(virtualDeviceId)) {
- Slog.w(TAG,
- "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
- + " is invalid");
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
- }
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
+ String resolvedPackageName;
+ if (!shouldUseNewCheckOp()) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG, "checkOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + virtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
+ resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
+ } else {
+ // Note, this flag changes the behavior in this case:
+ // invalid package is now IGNORE instead of ERROR for consistency
+ resolvedPackageName = validateOpRequest(code, uid, packageName,
+ virtualDeviceId, true, "noteOperation");
+ if (resolvedPackageName == null) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
}
+
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
virtualDeviceId, Process.INVALID_UID, null, null,
Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
@@ -3145,7 +3287,6 @@ public class AppOpsService extends IAppOpsService.Stub {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- boolean wasNull = attributionTag == null;
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
@@ -3485,20 +3626,23 @@ public class AppOpsService extends IAppOpsService.Stub {
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks == null && binderFrozenStateChangeCallback()
+ && useFrozenAwareRemoteCallbackList()) {
+ callbacks = new RemoteCallbackList.Builder<IAppOpsAsyncNotedCallback>(
+ RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
+ .setInterfaceDiedCallback((rcl, cb, cookie) ->
+ stopWatchingAsyncNoted(packageName, callback)
+ ).build();
+ }
if (callbacks == null) {
callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
- @Override
- public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
- synchronized (AppOpsService.this) {
- if (getRegisteredCallbackCount() == 0) {
- mAsyncOpWatchers.remove(key);
- }
+ @Override
+ public void onCallbackDied(IAppOpsAsyncNotedCallback cb) {
+ stopWatchingAsyncNoted(packageName, callback);
}
- }
- };
- mAsyncOpWatchers.put(key, callbacks);
+ };
}
-
+ mAsyncOpWatchers.put(key, callbacks);
callbacks.register(callback);
}
}
@@ -3567,24 +3711,35 @@ public class AppOpsService extends IAppOpsService.Stub {
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isValidVirtualDeviceId(virtualDeviceId)) {
- Slog.w(TAG,
- "startOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
- + " is invalid");
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
- }
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
+ String resolvedPackageName;
+ if (!shouldUseNewCheckOp()) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG, "startOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + virtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
+ resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
+ } else {
+ // Note, this flag changes the behavior in this case:
+ // invalid package is now IGNORE instead of ERROR for consistency
+ resolvedPackageName = validateOpRequest(code, uid, packageName,
+ virtualDeviceId, true, "startOperation");
+ if (resolvedPackageName == null) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
}
// As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
@@ -4306,6 +4461,48 @@ public class AppOpsService extends IAppOpsService.Stub {
|| (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
}
+ private boolean shouldUseNewCheckOp() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return checkOpValidatePackage();
+ } catch (Exception e) {
+ // before device provider init, only on old storage
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Validates arguments for a particular op request
+ * @param shouldVerifyUid - If the calling uid needs perms for other uids, due to the method
+ * being an appop write.
+ * @param methodName - For logging purposes
+ * @return The resolved package for the request, null on any failure
+ */
+ private @Nullable String validateOpRequest(int code, int uid, String packageName, int vdi,
+ boolean shouldVerifyUid, String methodName) {
+ verifyIncomingOp(code);
+ if (shouldVerifyUid) {
+ verifyIncomingUid(uid);
+ }
+ if (!isValidVirtualDeviceId(vdi)) {
+ Slog.w(TAG, methodName + ": error due to virtualDeviceId " + vdi + " is invalid");
+ return null;
+ }
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ Slog.w(TAG, methodName + ": error due to package: " + packageName
+ + " is invalid for " + uid);
+ return null;
+ }
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ Slog.w(TAG, methodName + ": error due to unable to resolve uid: " + uid);
+ return null;
+ }
+ return resolvedPackageName;
+ }
+
private void verifyIncomingProxyUid(@NonNull AttributionSource attributionSource) {
if (attributionSource.getUid() == Binder.getCallingUid()) {
return;
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 03c81560be89..fa2e674d37c7 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -31,11 +31,11 @@ import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS;
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_NONEXISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates;
import static android.permission.flags.Flags.finishRunningOpsForKilledPackages;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
@@ -176,8 +176,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
case OP_RECORD_AUDIO:
case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
return PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
- case OP_TAKE_AUDIO_FOCUS:
- return PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
default:
return PROCESS_CAPABILITY_NONE;
}
@@ -236,20 +234,26 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
mPendingUidStates.put(uid, uidState);
mPendingCapability.put(uid, capability);
+ boolean hasLostCapability = (prevCapability & ~capability) != 0;
+
if (procState == PROCESS_STATE_NONEXISTENT) {
mPendingGone.put(uid, true);
commitUidPendingState(uid);
- } else if (uidState < prevUidState
- || (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- && prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) {
+ } else if (uidState < prevUidState) {
// We are moving to a more important state, or the new state may be in the
// foreground and the old state is in the background, then always do it
// immediately.
commitUidPendingState(uid);
- } else if (uidState == prevUidState && capability != prevCapability) {
+ } else if (delayUidStateChangesFromCapabilityUpdates()
+ && uidState == prevUidState && !hasLostCapability) {
+ // No change on process state, but process capability hasn't decreased.
+ commitUidPendingState(uid);
+ } else if (!delayUidStateChangesFromCapabilityUpdates()
+ && uidState == prevUidState && capability != prevCapability) {
// No change on process state, but process capability has changed.
commitUidPendingState(uid);
- } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED) {
+ } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ && (!delayUidStateChangesFromCapabilityUpdates() || !hasLostCapability)) {
// We are moving to a less important state, but it doesn't cross the restriction
// threshold.
commitUidPendingState(uid);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 44034896ce36..a3b20b93ef02 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,7 @@ import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.content.AttributionSource;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -306,14 +307,13 @@ public class AudioDeviceBroker {
* @param on true to enable speakerphone
* @param eventSource for logging purposes
*/
- /*package*/ void setSpeakerphoneOn(
- IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
-
+ /*package*/ void setSpeakerphoneOn(IBinder cb, @NonNull AttributionSource attributionSource,
+ boolean on, boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
+ Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + attributionSource.getUid());
}
- postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
- cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(cb, attributionSource,
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
on, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
}
@@ -332,16 +332,18 @@ public class AudioDeviceBroker {
* @param eventSource for logging purposes
* @return false if there is no device and no communication client
*/
- /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
- boolean isPrivileged, String eventSource) {
-
+ /*package*/ boolean setCommunicationDevice(IBinder cb,
+ @NonNull AttributionSource attributionSource, AudioDeviceInfo device,
+ boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
+ Log.v(TAG, "setCommunicationDevice, device: " + device
+ + ", uid: " + attributionSource.getUid());
}
if (device == null) {
synchronized (mDeviceStateLock) {
- CommunicationRouteClient client = getCommunicationRouteClientForUid(uid);
+ CommunicationRouteClient client =
+ getCommunicationRouteClientForUid(attributionSource.getUid());
if (client == null) {
return false;
}
@@ -351,7 +353,8 @@ public class AudioDeviceBroker {
mCommunicationDeviceUpdateCount++;
AudioDeviceAttributes deviceAttr =
(device != null) ? new AudioDeviceAttributes(device) : null;
- CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
+ CommunicationDeviceInfo deviceInfo =
+ new CommunicationDeviceInfo(cb, attributionSource, deviceAttr,
device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged);
postSetCommunicationDeviceForClient(deviceInfo);
}
@@ -369,7 +372,8 @@ public class AudioDeviceBroker {
Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
}
if (!deviceInfo.mOn) {
- CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
+ CommunicationRouteClient client =
+ getCommunicationRouteClientForUid(deviceInfo.mAttributionSource.getUid());
if (client == null || (deviceInfo.mDevice != null
&& !deviceInfo.mDevice.equals(client.getDevice()))) {
return;
@@ -377,51 +381,60 @@ public class AudioDeviceBroker {
}
AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
- setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
- deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
+ setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mAttributionSource,
+ device, deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged,
+ deviceInfo.mEventSource);
}
/**
* Indicates if a Bluetooth SCO activation request owner is controlling
* the SCO audio state itself or not.
- * @param uid the UI of the SOC request owner app
+ * @param attributionSource the AttributionSource of the SCO request owner app
* @return true if we should control SCO audio state, false otherwise
*/
- private boolean shouldStartScoForUid(int uid) {
- return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID);
+ private boolean shouldStartScoForAttributionSource(AttributionSource attributionSource) {
+ if (attributionSource == null) {
+ return true;
+ }
+ int uid = attributionSource.getUid();
+ return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
+ || UserHandle.isSameApp(uid, Process.PHONE_UID)
+ || (UserHandle.isSameApp(uid, Process.SYSTEM_UID)
+ && "com.android.server.telecom".equals(attributionSource.getPackageName())));
}
@GuardedBy("mDeviceStateLock")
/*package*/ void setCommunicationRouteForClient(
- IBinder cb, int uid, AudioDeviceAttributes device,
- int scoAudioMode, boolean isPrivileged, String eventSource) {
-
+ IBinder cb, @NonNull AttributionSource attributionSource, AudioDeviceAttributes device,
+ int scoAudioMode, boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setCommunicationRouteForClient: device: " + device
+ ", eventSource: " + eventSource);
}
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "setCommunicationRouteForClient for uid: " + uid
+ "setCommunicationRouteForClient for uid: "
+ + attributionSource.getUid()
+ " device: " + device + " isPrivileged: " + isPrivileged
+ " from API: " + eventSource)).printLog(TAG));
- final int previousBtScoRequesterUid = bluetoothScoRequestOwnerUid();
+ final AttributionSource previousBtScoRequesterAS =
+ bluetoothScoRequestOwnerAttributionSource();
CommunicationRouteClient client;
// Save previous client route in case of failure to start BT SCO audio
AudioDeviceAttributes prevClientDevice = null;
boolean prevPrivileged = false;
- client = getCommunicationRouteClientForUid(uid);
+ client = getCommunicationRouteClientForUid(attributionSource.getUid());
if (client != null) {
prevClientDevice = client.getDevice();
prevPrivileged = client.isPrivileged();
}
if (device != null) {
- client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
+ client = addCommunicationRouteClient(cb, attributionSource, device, isPrivileged);
if (client == null) {
Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
- + uid + " and device: " + device);
+ + attributionSource.getUid() + " and device: " + device);
}
} else {
client = removeCommunicationRouteClient(cb, true);
@@ -429,22 +442,23 @@ public class AudioDeviceBroker {
if (client == null) {
return;
}
- final int btScoRequesterUid = bluetoothScoRequestOwnerUid();
- final boolean isBtScoRequested = btScoRequesterUid != -1;
- final boolean wasBtScoRequested = previousBtScoRequesterUid != -1;
+ final AttributionSource btScoRequesterAS = bluetoothScoRequestOwnerAttributionSource();
+ final boolean isBtScoRequested = btScoRequesterAS != null;
+ final boolean wasBtScoRequested = previousBtScoRequesterAS != null;
if (mScoManagedByAudio) {
if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive()
|| !mBtHelper.isBluetoothScoRequestedInternally())) {
boolean scoStarted = false;
- if (shouldStartScoForUid(btScoRequesterUid)) {
+ if (shouldStartScoForAttributionSource(btScoRequesterAS)) {
scoStarted = mBtHelper.startBluetoothSco(scoAudioMode, eventSource);
if (!scoStarted) {
Log.w(TAG, "setCommunicationRouteForClient: "
- + "failure to start BT SCO for uid: " + uid);
+ + "failure to start BT SCO for uid: " + attributionSource.getUid());
// clean up or restore previous client selection
if (prevClientDevice != null) {
- addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+ addCommunicationRouteClient(cb, attributionSource,
+ prevClientDevice, prevPrivileged);
} else {
removeCommunicationRouteClient(cb, true);
}
@@ -457,7 +471,7 @@ public class AudioDeviceBroker {
setBluetoothScoOn(true, "setCommunicationRouteForClient");
}
} else if (!isBtScoRequested && wasBtScoRequested) {
- if (shouldStartScoForUid(previousBtScoRequesterUid)) {
+ if (shouldStartScoForAttributionSource(previousBtScoRequesterAS)) {
mBtHelper.stopBluetoothSco(eventSource);
}
setBluetoothScoOn(false, "setCommunicationRouteForClient");
@@ -467,10 +481,11 @@ public class AudioDeviceBroker {
|| !mBtHelper.isBluetoothScoRequestedInternally())) {
if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
- + uid);
+ + attributionSource.getUid());
// clean up or restore previous client selection
if (prevClientDevice != null) {
- addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+ addCommunicationRouteClient(cb, attributionSource,
+ prevClientDevice, prevPrivileged);
} else {
removeCommunicationRouteClient(cb, true);
}
@@ -581,7 +596,7 @@ public class AudioDeviceBroker {
// Cancelling the route for this client will remove it from the stack and update
// the communication route.
CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
- crc.getBinder(), crc.getUid(), device, false,
+ crc.getBinder(), crc.getAttributionSource(), device, false,
BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
crc.isPrivileged());
postSetCommunicationDeviceForClient(deviceInfo);
@@ -617,12 +632,11 @@ public class AudioDeviceBroker {
@GuardedBy("mDeviceStateLock")
/*package*/ void updateCommunicationRouteClientState(
CommunicationRouteClient client, boolean wasActive) {
- int btScoRequesterUid = bluetoothScoRequestOwnerUid();
client.setPlaybackActive(mAudioService.isPlaybackActiveForUid(client.getUid()));
client.setRecordingActive(mAudioService.isRecordingActiveForUid(client.getUid()));
if (wasActive != client.isActive()) {
- postUpdateCommunicationRouteClient(
- btScoRequesterUid, "updateCommunicationRouteClientState");
+ postUpdateCommunicationRouteClient(bluetoothScoRequestOwnerAttributionSource(),
+ "updateCommunicationRouteClientState");
}
}
@@ -679,6 +693,8 @@ public class AudioDeviceBroker {
elapsed = System.currentTimeMillis() - start;
if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
Log.e(TAG, "Timeout waiting for communication device update.");
+ // reset counter to avoid sticky out of sync condition
+ mCommunicationDeviceUpdateCount = 0;
break;
}
}
@@ -808,21 +824,26 @@ public class AudioDeviceBroker {
}
/**
- * Helper method on top of isBluetoothScoRequested() returning the UID of the
- * BT SCO route request owner of -1 if SCO is not requested.
- * @return the UID of the BT SCO route request owner of -1 if SCO is not requested.
+ * Helper method on top of isBluetoothScoRequested() returning the Attribution Source of the
+ * BT SCO route request owner or null if SCO is not requested.
+ * @return the AttributionSource of the BT SCO route request owner of null.
*/
@GuardedBy("mDeviceStateLock")
- /*package*/ int bluetoothScoRequestOwnerUid() {
+ /*package*/ @Nullable AttributionSource bluetoothScoRequestOwnerAttributionSource() {
if (!isBluetoothScoRequested()) {
- return -1;
+ return null;
}
CommunicationRouteClient crc = topCommunicationRouteClient();
if (crc == null) {
- return -1;
+ return null;
}
- return crc.getUid();
+ return crc.getAttributionSource();
}
+
+ private static int safeUidFromAttributionSource(AttributionSource attributionSource) {
+ return (attributionSource != null) ? attributionSource.getUid() : -1;
+ }
+
/**
* Helper method on top of isDeviceRequestedForCommunication() indicating if
* Bluetooth LE Audio communication device is currently requested or not.
@@ -1220,14 +1241,15 @@ public class AudioDeviceBroker {
@GuardedBy("mDeviceStateLock")
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
synchronized (mBluetoothAudioStateLock) {
- int btScoRequesterUId = bluetoothScoRequestOwnerUid();
+ AttributionSource btScoRequesterAS = bluetoothScoRequestOwnerAttributionSource();
Log.i(TAG, "setBluetoothScoOn: " + on + ", mBluetoothScoOn: "
- + mBluetoothScoOn + ", btScoRequesterUId: " + btScoRequesterUId
+ + mBluetoothScoOn + ", btScoRequesterUId: "
+ + safeUidFromAttributionSource(btScoRequesterAS)
+ ", from: " + eventSource);
mBluetoothScoOn = on;
updateAudioHalBluetoothState();
if (!mScoManagedByAudio) {
- postUpdateCommunicationRouteClient(btScoRequesterUId, eventSource);
+ postUpdateCommunicationRouteClient(btScoRequesterAS, eventSource);
}
}
}
@@ -1321,34 +1343,35 @@ public class AudioDeviceBroker {
sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
}
- /*package*/ void postSetModeOwner(int mode, int pid, int uid) {
- sendLMsgNoDelay(MSG_I_SET_MODE_OWNER, SENDMSG_REPLACE,
- new AudioModeInfo(mode, pid, uid));
+ /*package*/ void postSetModeOwner(int mode, int pid, int uid, boolean signal) {
+ sendLMsgNoDelay(signal ? MSG_L_SET_MODE_OWNER_SIGNAL : MSG_L_SET_MODE_OWNER,
+ SENDMSG_REPLACE, new AudioModeInfo(mode, pid, uid));
}
/*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
}
- /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
- boolean isPrivileged, @NonNull String eventSource) {
-
+ /*package*/ void startBluetoothScoForClient(IBinder cb,
+ @NonNull AttributionSource attributionSource, int scoAudioMode, boolean isPrivileged,
+ @NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
+ Log.v(TAG, "startBluetoothScoForClient, uid: " + attributionSource.getUid());
}
- postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
- cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(cb, attributionSource,
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
true, scoAudioMode, eventSource, isPrivileged));
}
- /*package*/ void stopBluetoothScoForClient(
- IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {
-
+ /*package*/ void stopBluetoothScoForClient(IBinder cb,
+ @NonNull AttributionSource attributionSource, boolean isPrivileged,
+ @NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
+ Log.v(TAG, "stopBluetoothScoForClient, uid: " + attributionSource.getUid());
}
postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
- cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ cb, attributionSource, new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
false, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
}
@@ -1564,10 +1587,21 @@ public class AudioDeviceBroker {
sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client);
}
+ private static final class UpdateCommRouteClientInfo {
+ @NonNull public final AttributionSource attributionSource;
+ @NonNull public final String eventSource;
+
+ UpdateCommRouteClientInfo(@NonNull AttributionSource attributionSource,
+ @NonNull String eventSource) {
+ this.attributionSource = attributionSource;
+ this.eventSource = eventSource;
+ }
+ }
+
/*package*/ void postUpdateCommunicationRouteClient(
- int btScoRequesterUid, String eventSource) {
- sendILMsgNoDelay(MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
- btScoRequesterUid, eventSource);
+ AttributionSource attributionSource, String eventSource) {
+ sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
+ new UpdateCommRouteClientInfo(attributionSource, eventSource));
}
/*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
@@ -1598,18 +1632,18 @@ public class AudioDeviceBroker {
/*package*/ static final class CommunicationDeviceInfo {
final @NonNull IBinder mCb; // Identifies the requesting client for death handler
- final int mUid; // Requester UID
+ final @NonNull AttributionSource mAttributionSource; // Requester attribution source
final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
final boolean mOn; // true if setting, false if resetting
final int mScoAudioMode; // only used for SCO: requested audio mode
final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
final @NonNull String mEventSource; // caller identifier for logging
- CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
+ CommunicationDeviceInfo(@NonNull IBinder cb, @NonNull AttributionSource attributionSource,
@Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
@NonNull String eventSource, boolean isPrivileged) {
mCb = cb;
- mUid = uid;
+ mAttributionSource = attributionSource;
mDevice = device;
mOn = on;
mScoAudioMode = scoAudioMode;
@@ -1631,19 +1665,19 @@ public class AudioDeviceBroker {
}
return mCb.equals(((CommunicationDeviceInfo) o).mCb)
- && mUid == ((CommunicationDeviceInfo) o).mUid;
+ && mAttributionSource.equals(((CommunicationDeviceInfo) o).mAttributionSource);
}
@Override
public int hashCode() {
// only hashing on the fields used in equals()
- return Objects.hash(mCb.hashCode(), mUid);
+ return Objects.hash(mCb.hashCode(), mAttributionSource);
}
@Override
public String toString() {
return "CommunicationDeviceInfo mCb=" + mCb.toString()
- + " mUid=" + mUid
+ + " mAttributionSource=" + mAttributionSource.toString()
+ " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
+ " mOn=" + mOn
+ " mScoAudioMode=" + mScoAudioMode
@@ -1927,7 +1961,8 @@ public class AudioDeviceBroker {
|| btInfo.mProfile == BluetoothProfile.HEARING_AID
|| (mScoManagedByAudio
&& btInfo.mProfile == BluetoothProfile.HEADSET)) {
- onUpdateCommunicationRouteClient(bluetoothScoRequestOwnerUid(),
+ onUpdateCommunicationRouteClient(
+ bluetoothScoRequestOwnerAttributionSource(),
"setBluetoothActiveDevice");
}
}
@@ -1993,17 +2028,21 @@ public class AudioDeviceBroker {
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
}
break;
- case MSG_I_SET_MODE_OWNER:
+ case MSG_L_SET_MODE_OWNER:
+ case MSG_L_SET_MODE_OWNER_SIGNAL:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- int btScoRequesterUid = bluetoothScoRequestOwnerUid();
mAudioModeOwner = (AudioModeInfo) msg.obj;
if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
onUpdateCommunicationRouteClient(
- btScoRequesterUid, "setNewModeOwner");
+ bluetoothScoRequestOwnerAttributionSource(),
+ "setNewModeOwner");
}
}
}
+ if (msg.what == MSG_L_SET_MODE_OWNER_SIGNAL) {
+ mAudioService.decrementAudioModeResetCount();
+ }
break;
case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT:
@@ -2024,10 +2063,12 @@ public class AudioDeviceBroker {
}
break;
- case MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT:
+ case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- onUpdateCommunicationRouteClient(msg.arg1, (String) msg.obj);
+ UpdateCommRouteClientInfo info = (UpdateCommRouteClientInfo) msg.obj;
+ onUpdateCommunicationRouteClient(
+ info.attributionSource, info.eventSource);
}
}
break;
@@ -2161,7 +2202,8 @@ public class AudioDeviceBroker {
private static final int MSG_REPORT_NEW_ROUTES = 13;
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
- private static final int MSG_I_SET_MODE_OWNER = 16;
+ private static final int MSG_L_SET_MODE_OWNER = 16;
+ private static final int MSG_L_SET_MODE_OWNER_SIGNAL = 17;
private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
@@ -2177,7 +2219,7 @@ public class AudioDeviceBroker {
private static final int MSG_REPORT_NEW_ROUTES_A2DP = 36;
private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
- private static final int MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
+ private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
//
@@ -2389,20 +2431,20 @@ public class AudioDeviceBroker {
private class CommunicationRouteClient implements IBinder.DeathRecipient {
private final IBinder mCb;
- private final int mUid;
+ @NonNull private final AttributionSource mAttributionSource;
private final boolean mIsPrivileged;
private AudioDeviceAttributes mDevice;
private boolean mPlaybackActive;
private boolean mRecordingActive;
- CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
- boolean isPrivileged) {
+ CommunicationRouteClient(IBinder cb, @NonNull AttributionSource attributionSource,
+ AudioDeviceAttributes device, boolean isPrivileged) {
mCb = cb;
- mUid = uid;
+ mAttributionSource = attributionSource;
mDevice = device;
mIsPrivileged = isPrivileged;
- mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
- mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
+ mPlaybackActive = mAudioService.isPlaybackActiveForUid(attributionSource.getUid());
+ mRecordingActive = mAudioService.isRecordingActiveForUid(attributionSource.getUid());
}
public boolean registerDeathRecipient() {
@@ -2433,8 +2475,12 @@ public class AudioDeviceBroker {
return mCb;
}
+ @NonNull AttributionSource getAttributionSource() {
+ return mAttributionSource;
+ }
+
int getUid() {
- return mUid;
+ return mAttributionSource.getUid();
}
boolean isPrivileged() {
@@ -2459,7 +2505,7 @@ public class AudioDeviceBroker {
@Override
public String toString() {
- return "[CommunicationRouteClient: mUid: " + mUid
+ return "[CommunicationRouteClient: mAttributionSource: " + mAttributionSource
+ " mDevice: " + mDevice.toString()
+ " mIsPrivileged: " + mIsPrivileged
+ " mPlaybackActive: " + mPlaybackActive
@@ -2474,8 +2520,8 @@ public class AudioDeviceBroker {
return;
}
Log.w(TAG, "Communication client died");
- setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
- BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
+ setCommunicationRouteForClient(client.getBinder(), client.getAttributionSource(),
+ null, BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
"onCommunicationRouteClientDied");
}
@@ -2556,21 +2602,22 @@ public class AudioDeviceBroker {
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(
- int previousBtScoRequesterUid, String eventSource) {
+ @Nullable AttributionSource previousBtScoRequesterAS, String eventSource) {
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
- + " previousBtScoRequesterUid: " + previousBtScoRequesterUid
+ + " previous BT SCO Requester UID: "
+ + safeUidFromAttributionSource(previousBtScoRequesterAS)
+ " eventSource: " + eventSource);
}
if (crc != null) {
- setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
- BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
+ setCommunicationRouteForClient(crc.getBinder(), crc.getAttributionSource(),
+ crc.getDevice(), BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
} else {
- boolean wasScoRequested = previousBtScoRequesterUid != -1;
+ boolean wasScoRequested = previousBtScoRequesterAS != null;
if (!isBluetoothScoRequested() && wasScoRequested) {
if (mScoManagedByAudio) {
- if (shouldStartScoForUid(previousBtScoRequesterUid)) {
+ if (shouldStartScoForAttributionSource(previousBtScoRequesterAS)) {
mBtHelper.stopBluetoothSco(eventSource);
}
setBluetoothScoOn(false, eventSource);
@@ -2598,8 +2645,8 @@ public class AudioDeviceBroker {
Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
}
}
- mAudioService.postUpdateRingerModeServiceInt();
dispatchCommunicationDevice();
+ mAudioService.postUpdateRingerModeServiceInt();
}
@GuardedBy("mDeviceStateLock")
@@ -2619,12 +2666,13 @@ public class AudioDeviceBroker {
}
@GuardedBy("mDeviceStateLock")
- private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
- AudioDeviceAttributes device, boolean isPrivileged) {
+ private CommunicationRouteClient addCommunicationRouteClient(
+ IBinder cb, @NonNull AttributionSource attributionSource, AudioDeviceAttributes device,
+ boolean isPrivileged) {
// always insert new request at first position
removeCommunicationRouteClient(cb, true);
CommunicationRouteClient client =
- new CommunicationRouteClient(cb, uid, device, isPrivileged);
+ new CommunicationRouteClient(cb, attributionSource, device, isPrivileged);
if (client.registerDeathRecipient()) {
mCommunicationRouteClients.add(0, client);
if (!client.isActive()) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 34d4fb02ad99..acb46d9b85e6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -793,6 +793,7 @@ public class AudioDeviceInventory {
* (see AudioService.onAudioServerDied() method)
*/
// Always executed on AudioDeviceBroker message queue
+ @GuardedBy("mDeviceBroker.mDeviceStateLock")
/*package*/ void onRestoreDevices() {
synchronized (mDevicesLock) {
int res;
@@ -815,6 +816,9 @@ public class AudioDeviceInventory {
"Device inventory restore failed to reconnect " + di,
EventLogger.Event.ALOGE, TAG);
mConnectedDevices.remove(di.getKey(), di);
+ if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+ mDeviceBroker.onSetBtScoActiveDevice(null);
+ }
}
}
mAppliedStrategyRolesInt.clear();
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 5283eddd90fb..7502664a9628 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -24,8 +24,10 @@ import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
import static android.Manifest.permission.CAPTURE_MEDIA_OUTPUT;
import static android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT;
import static android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT;
+import static android.Manifest.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
import static android.Manifest.permission.MODIFY_AUDIO_ROUTING;
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS;
+import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.RECORD_AUDIO;
@@ -37,7 +39,6 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IntArray;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.media.permission.INativePermissionController;
@@ -82,6 +83,10 @@ public class AudioServerPermissionProvider {
MONITORED_PERMS[PermissionEnum.CAPTURE_VOICE_COMMUNICATION_OUTPUT] =
CAPTURE_VOICE_COMMUNICATION_OUTPUT;
MONITORED_PERMS[PermissionEnum.BLUETOOTH_CONNECT] = BLUETOOTH_CONNECT;
+ MONITORED_PERMS[PermissionEnum.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION] =
+ BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
+ MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS_PRIVILEGED] =
+ MODIFY_AUDIO_SETTINGS_PRIVILEGED;
}
private final Object mLock = new Object();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8d0118173030..3cc89a5744c7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -49,6 +49,7 @@ import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.concurrentAudioRecordBypassPermission;
import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
import static android.media.audio.Flags.focusFreezeTestApi;
import static android.media.audio.Flags.roForegroundAudioControl;
@@ -68,9 +69,11 @@ import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
import static com.android.media.audio.Flags.replaceStreamBtSco;
+import static com.android.media.audio.Flags.ringMyCar;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;
+import static com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -92,6 +95,7 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.PropertyInvalidatedCache;
import android.app.UidObserver;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -245,6 +249,7 @@ import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -455,7 +460,7 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_UPDATE_AUDIO_MODE = 36;
private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
private static final int MSG_BT_DEV_CHANGED = 38;
-
+ private static final int MSG_UPDATE_AUDIO_MODE_SIGNAL = 39;
private static final int MSG_DISPATCH_AUDIO_MODE = 40;
private static final int MSG_ROUTING_UPDATED = 41;
private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
@@ -490,6 +495,10 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_INIT_SPATIALIZER = 102;
private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
+ private static final int MSG_INIT_INPUT_GAINS = 104;
+ private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
+ private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
+
// end of messages handled under wakelock
// retry delay in case of failure to indicate system ready to AudioFlinger
@@ -511,6 +520,11 @@ public class AudioService extends IAudioService.Stub
**/
private SparseArray<VolumeStreamState> mStreamStates;
+ /**
+ * @see InputDeviceVolumeHelper
+ */
+ private InputDeviceVolumeHelper mInputDeviceVolumeHelper;
+
/*package*/ int getVssVolumeForDevice(int stream, int device) {
final VolumeStreamState streamState = mStreamStates.get(stream);
return streamState != null ? streamState.getIndex(device) : -1;
@@ -761,7 +775,7 @@ public class AudioService extends IAudioService.Stub
/** Streams that can be muted by system. Do not resolve to aliases when checking.
* @see System#MUTE_STREAMS_AFFECTED */
- private int mMuteAffectedStreams;
+ protected int mMuteAffectedStreams;
/** Streams that can be muted by user. Do not resolve to aliases when checking.
* @see System#MUTE_STREAMS_AFFECTED */
@@ -1465,7 +1479,8 @@ public class AudioService extends IAudioService.Stub
mPlaybackMonitor =
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
- device -> onMuteAwaitConnectionTimeout(device));
+ device -> onMuteAwaitConnectionTimeout(device),
+ stream -> isStreamMute(stream));
mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1499,6 +1514,15 @@ public class AudioService extends IAudioService.Stub
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
+ if (enableAudioInputDeviceRoutingAndVolumeControl()) {
+ queueMsgUnderWakeLock(
+ mAudioHandler,
+ MSG_INIT_INPUT_GAINS,
+ 0 /* arg1 */,
+ 0 /* arg2 */,
+ null /* obj */,
+ 0 /* delay */);
+ }
mDisplayManager = context.getSystemService(DisplayManager.class);
@@ -1587,11 +1611,27 @@ public class AudioService extends IAudioService.Stub
if (dev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
enabled = mAvrcpAbsVolSupported;
}
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", enabled, stream);
+ final int result = mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"",
+ enabled, stream);
+ if (result != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueueAndSlog(
+ new VolumeEvent(VolumeEvent.VOL_ABS_DEVICE_ENABLED_ERROR,
+ result, dev, enabled, stream).eventToString(), ALOGE, TAG);
+
+ }
});
}
}
+ /** Called by handling of MSG_INIT_INPUT_GAINS */
+ private void onInitInputGains() {
+ mInputDeviceVolumeHelper =
+ new InputDeviceVolumeHelper(
+ mSettings,
+ mContentResolver,
+ System.INPUT_GAIN_INDEX_SETTINGS);
+ }
+
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
@@ -1918,7 +1958,7 @@ public class AudioService extends IAudioService.Stub
// Restore call state
synchronized (mDeviceBroker.mSetModeLock) {
onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
- mContext.getPackageName(), true /*force*/);
+ mContext.getPackageName(), true /*force*/, false /*signal*/);
}
final int forSys;
synchronized (mSettingsLock) {
@@ -2032,16 +2072,6 @@ public class AudioService extends IAudioService.Stub
onIndicateSystemReady();
- synchronized (mCachedAbsVolDrivingStreamsLock) {
- mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
- boolean enabled = true;
- if (dev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- enabled = mAvrcpAbsVolSupported;
- }
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", enabled, stream);
- });
- }
-
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
@@ -2156,6 +2186,22 @@ public class AudioService extends IAudioService.Stub
return;
}
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
+ boolean enabled = true;
+ if (dev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ enabled = mAvrcpAbsVolSupported;
+ }
+ final int result = mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"",
+ enabled, stream);
+ if (result != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueueAndSlog(
+ new VolumeEvent(VolumeEvent.VOL_ABS_DEVICE_ENABLED_ERROR,
+ result, dev, enabled, stream).eventToString(), ALOGE, TAG);
+ }
+ });
+ }
+
// did it work? check based on min/max values of some basic streams
if (!checkVolumeRangeInitialization(caller)) {
return;
@@ -2747,6 +2793,11 @@ public class AudioService extends IAudioService.Stub
}
}
+ @Override
+ protected void onUnhandledException(int code, int flags, Exception e) {
+ Slog.wtf(TAG, "Uncaught exception in AudioService: " + code + ", " + flags, e);
+ }
+
@Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -3560,8 +3611,10 @@ public class AudioService extends IAudioService.Stub
* @see AudioManager#addOnDevicesForAttributesChangedListener(
* AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
*/
+ @android.annotation.EnforcePermission(anyOf = { MODIFY_AUDIO_ROUTING, QUERY_AUDIO_STATE })
public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
IDevicesForAttributesCallback callback) {
+ super.addOnDevicesForAttributesChangedListener_enforcePermission();
mAudioSystem.addOnDevicesForAttributesChangedListener(
attributes, false /* forVolume */, callback);
}
@@ -4030,7 +4083,6 @@ public class AudioService extends IAudioService.Stub
&& isFullVolumeDevice(device);
boolean tvConditions = mHdmiTvClient != null
&& mHdmiSystemAudioSupported
- && isFullVolumeDevice(device)
&& !isAbsoluteVolumeDevice(device)
&& !isA2dpAbsoluteVolumeDevice(device);
@@ -4125,12 +4177,6 @@ public class AudioService extends IAudioService.Stub
// Stream mute changed, fire the intent.
Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
- if (replaceStreamBtSco() && isStreamBluetoothSco(streamType)) {
- intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- AudioSystem.STREAM_BLUETOOTH_SCO);
- // in this case broadcast for both sco and voice_call streams the mute status
- sendBroadcastToAll(intent, null /* options */);
- }
intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
sendBroadcastToAll(intent, null /* options */);
}
@@ -4638,6 +4684,12 @@ public class AudioService extends IAudioService.Stub
switch (mode) {
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
+ // TODO(b/382704431): remove to allow STREAM_VOICE_CALL to drive abs volume
+ // over A2DP
+ if (getDeviceForStream(AudioSystem.STREAM_VOICE_CALL)
+ == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ return AudioSystem.STREAM_MUSIC;
+ }
return AudioSystem.STREAM_VOICE_CALL;
case AudioSystem.MODE_CALL_SCREENING:
case AudioSystem.MODE_COMMUNICATION_REDIRECT:
@@ -4649,15 +4701,20 @@ public class AudioService extends IAudioService.Stub
// other conditions will influence the stream type choice, read on...
break;
}
- if (voiceActivityCanOverride
- && mVoicePlaybackActive.get()) {
+
+ if (voiceActivityCanOverride && mVoicePlaybackActive.get()) {
+ // TODO(b/382704431): remove to allow STREAM_VOICE_CALL to drive abs volume over A2DP
+ if (getDeviceForStream(AudioSystem.STREAM_VOICE_CALL)
+ == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ return AudioSystem.STREAM_MUSIC;
+ }
return AudioSystem.STREAM_VOICE_CALL;
}
return AudioSystem.STREAM_MUSIC;
}
- private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
- private AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
+ private final AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
+ private final AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
private final IPlaybackConfigDispatcher mPlaybackActivityMonitor =
new IPlaybackConfigDispatcher.Stub() {
@@ -4746,17 +4803,45 @@ public class AudioService extends IAudioService.Stub
}
}
if (updateAudioMode) {
- sendMsg(mAudioHandler,
- MSG_UPDATE_AUDIO_MODE,
- existingMsgPolicy,
- AudioSystem.MODE_CURRENT,
- android.os.Process.myPid(),
- mContext.getPackageName(),
- delay);
+ postUpdateAudioMode(existingMsgPolicy, AudioSystem.MODE_CURRENT,
+ android.os.Process.myPid(), mContext.getPackageName(),
+ false /*signal*/, delay);
}
}
}
+ static class UpdateAudioModeInfo {
+ UpdateAudioModeInfo(int mode, int pid, String packageName) {
+ mMode = mode;
+ mPid = pid;
+ mPackageName = packageName;
+ }
+ private final int mMode;
+ private final int mPid;
+ private final String mPackageName;
+
+ int getMode() {
+ return mMode;
+ }
+ int getPid() {
+ return mPid;
+ }
+ String getPackageName() {
+ return mPackageName;
+ }
+ }
+
+ void postUpdateAudioMode(int msgPolicy, int mode, int pid, String packageName,
+ boolean signal, int delay) {
+ synchronized (mAudioModeResetLock) {
+ if (signal) {
+ mAudioModeResetCount++;
+ }
+ sendMsg(mAudioHandler, signal ? MSG_UPDATE_AUDIO_MODE_SIGNAL : MSG_UPDATE_AUDIO_MODE,
+ msgPolicy, 0, 0, new UpdateAudioModeInfo(mode, pid, packageName), delay);
+ }
+ }
+
private final IRecordingConfigDispatcher mVoiceRecordingActivityMonitor =
new IRecordingConfigDispatcher.Stub() {
@Override
@@ -4809,6 +4894,10 @@ public class AudioService extends IAudioService.Stub
+ replaceStreamBtSco());
pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:"
+ equalScoLeaVcIndexRange());
+ pw.println("\tcom.android.media.audio.ringMyCar:"
+ + ringMyCar());
+ pw.println("\tandroid.media.audio.Flags.concurrentAudioRecordBypassPermission:"
+ + concurrentAudioRecordBypassPermission());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -4845,41 +4934,55 @@ public class AudioService extends IAudioService.Stub
private void onUpdateContextualVolumes() {
final int streamType = getBluetoothContextualVolumeStream();
+ Slog.i(TAG,
+ "onUpdateContextualVolumes: absolute volume driving streams " + streamType
+ + " avrcp supported: " + mAvrcpAbsVolSupported);
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> {
boolean enabled = true;
if (absDev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
enabled = mAvrcpAbsVolSupported;
+ if (!enabled) {
+ Slog.w(TAG, "Updating avrcp not supported in onUpdateContextualVolumes");
+ }
}
- if (stream != streamType || !enabled) {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/"",
- enabled, streamType);
+ if (stream != streamType) {
+ final int result = mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev,
+ /*address=*/"", enabled, streamType);
+ if (result != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueueAndSlog(
+ new VolumeEvent(VolumeEvent.VOL_ABS_DEVICE_ENABLED_ERROR,
+ result, absDev, enabled, streamType).eventToString(), ALOGE,
+ TAG);
+ }
}
return streamType;
});
}
final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
- final Set<Integer> absVolumeMultiModeCaseDevices =
- AudioSystem.intersectionAudioDeviceTypes(
- mAbsVolumeMultiModeCaseDevices, deviceTypes);
- if (absVolumeMultiModeCaseDevices.isEmpty()) {
+ Set<Integer> absVolumeDeviceTypes = new ArraySet<>(
+ AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+ absVolumeDeviceTypes.addAll(mAbsVolumeMultiModeCaseDevices);
+
+ final Set<Integer> absVolumeDevices =
+ AudioSystem.intersectionAudioDeviceTypes(absVolumeDeviceTypes, deviceTypes);
+ if (absVolumeDevices.isEmpty()) {
return;
}
- if (absVolumeMultiModeCaseDevices.size() > 1) {
- Log.w(TAG, "onUpdateContextualVolumes too many active devices: "
- + absVolumeMultiModeCaseDevices.stream().map(AudioSystem::getOutputDeviceName)
+ if (absVolumeDevices.size() > 1) {
+ Slog.w(TAG, "onUpdateContextualVolumes too many active devices: "
+ + absVolumeDevices.stream().map(AudioSystem::getOutputDeviceName)
.collect(Collectors.joining(","))
+ ", for stream: " + streamType);
return;
}
- final int device = absVolumeMultiModeCaseDevices.toArray(new Integer[0])[0].intValue();
-
+ final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue();
final int index = getStreamVolume(streamType, device);
if (DEBUG_VOL) {
- Log.i(TAG, "onUpdateContextualVolumes streamType: " + streamType
+ Slog.i(TAG, "onUpdateContextualVolumes streamType: " + streamType
+ ", device: " + AudioSystem.getOutputDeviceName(device)
+ ", index: " + index);
}
@@ -4889,6 +4992,8 @@ public class AudioService extends IAudioService.Stub
getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
+ } else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
} else {
return;
}
@@ -5699,6 +5804,90 @@ public class AudioService extends IAudioService.Stub
: aliasStreamType == sStreamVolumeAlias.get(AudioSystem.STREAM_SYSTEM);
}
+ /**
+ * @see AudioDeviceVolumeManager#setInputGainIndex(AudioDeviceAttributes, int)
+ */
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+ super.setInputGainIndex_enforcePermission();
+
+ if (mInputDeviceVolumeHelper.setInputGainIndex(ada, index)) {
+ // Post message to set system volume (it in turn will post a message
+ // to persist).
+ sendMsg(
+ mAudioHandler,
+ MSG_APPLY_INPUT_GAIN_INDEX,
+ SENDMSG_QUEUE,
+ /*arg1*/ index,
+ /*arg2*/ 0,
+ /*obj*/ ada,
+ /*delay*/ 0);
+ }
+ }
+
+ private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+ // TODO(b/364923030): call AudioSystem to apply input gain in native layer.
+
+ // Post a persist input gain msg.
+ sendMsg(
+ mAudioHandler,
+ MSG_PERSIST_INPUT_GAIN_INDEX,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0,
+ /*arg2*/ 0,
+ /*obj*/ ada,
+ PERSIST_DELAY);
+ }
+
+ private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ mInputDeviceVolumeHelper.persistInputGainIndex(ada);
+ }
+
+ /**
+ * @see AudioDeviceVolumeManager#getInputGainIndex(AudioDeviceAttributes)
+ */
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int getInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ super.getInputGainIndex_enforcePermission();
+
+ return mInputDeviceVolumeHelper.getInputGainIndex(ada);
+ }
+
+ /**
+ * @see AudioDeviceVolumeManager#getMaxInputGainIndex()
+ */
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int getMaxInputGainIndex() {
+ super.getMaxInputGainIndex_enforcePermission();
+
+ return mInputDeviceVolumeHelper.getMaxInputGainIndex();
+ }
+
+ /**
+ * @see AudioDeviceVolumeManager#getMinInputGainIndex()
+ */
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int getMinInputGainIndex() {
+ super.getMinInputGainIndex_enforcePermission();
+
+ return mInputDeviceVolumeHelper.getMinInputGainIndex();
+ }
+
+ /**
+ * @see AudioDeviceVolumeManager#isInputGainFixed(AudioDeviceAttributes)
+ */
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public boolean isInputGainFixed(@NonNull AudioDeviceAttributes ada) {
+ super.isInputGainFixed_enforcePermission();
+
+ return mInputDeviceVolumeHelper.isInputGainFixed(ada);
+ }
+
/** @see AudioManager#setMicrophoneMute(boolean) */
@Override
public void setMicrophoneMute(boolean on, String callingPackage, int userId,
@@ -6155,13 +6344,9 @@ public class AudioService extends IAudioService.Stub
} else {
SetModeDeathHandler h = mSetModeDeathHandlers.get(index);
mSetModeDeathHandlers.remove(index);
- sendMsg(mAudioHandler,
- MSG_UPDATE_AUDIO_MODE,
- SENDMSG_QUEUE,
- AudioSystem.MODE_CURRENT,
- android.os.Process.myPid(),
- mContext.getPackageName(),
- 0);
+ postUpdateAudioMode(SENDMSG_QUEUE, AudioSystem.MODE_CURRENT,
+ android.os.Process.myPid(), mContext.getPackageName(),
+ false /*signal*/, 0);
}
}
}
@@ -6407,19 +6592,14 @@ public class AudioService extends IAudioService.Stub
}
}
- sendMsg(mAudioHandler,
- MSG_UPDATE_AUDIO_MODE,
- SENDMSG_REPLACE,
- mode,
- pid,
- callingPackage,
- 0);
+ postUpdateAudioMode(SENDMSG_REPLACE, mode, pid, callingPackage,
+ hasModifyPhoneStatePermission && mode == AudioSystem.MODE_NORMAL, 0);
}
}
@GuardedBy("mDeviceBroker.mSetModeLock")
void onUpdateAudioMode(int requestedMode, int requesterPid, String requesterPackage,
- boolean force) {
+ boolean force, boolean signal) {
if (requestedMode == AudioSystem.MODE_CURRENT) {
requestedMode = getMode();
}
@@ -6434,7 +6614,7 @@ public class AudioService extends IAudioService.Stub
}
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode() new mode: " + mode + ", current mode: "
- + mMode.get() + " requested mode: " + requestedMode);
+ + mMode.get() + " requested mode: " + requestedMode + " signal: " + signal);
}
if (mode != mMode.get() || force) {
int status = AudioSystem.SUCCESS;
@@ -6480,8 +6660,11 @@ public class AudioService extends IAudioService.Stub
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
- mDeviceBroker.postSetModeOwner(mode, pid, uid);
+ mDeviceBroker.postSetModeOwner(mode, pid, uid, signal);
} else {
+ // reset here to avoid sticky out of sync condition (would have been reset
+ // by AudioDeviceBroker processing MSG_L_SET_MODE_OWNER_SIGNAL message)
+ resetAudioModeResetCount();
Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
}
}
@@ -6812,9 +6995,13 @@ public class AudioService extends IAudioService.Stub
* @see AudioManager#setCommunicationDevice(int)
* @see AudioManager#clearCommunicationDevice()
*/
- public boolean setCommunicationDevice(IBinder cb, int portId) {
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ public boolean setCommunicationDevice(IBinder cb, int portId,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource == null) {
+ return false;
+ }
+ final int uid = attributionSource.getUid();
+ final int pid = attributionSource.getPid();
AudioDeviceInfo device = null;
if (portId != 0) {
@@ -6864,7 +7051,8 @@ public class AudioService extends IAudioService.Stub
== PackageManager.PERMISSION_GRANTED;
final long ident = Binder.clearCallingIdentity();
try {
- return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource);
+ return mDeviceBroker.setCommunicationDevice(
+ cb, attributionSource, device, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6906,7 +7094,11 @@ public class AudioService extends IAudioService.Stub
}
/** @see AudioManager#setSpeakerphoneOn(boolean) */
- public void setSpeakerphoneOn(IBinder cb, boolean on) {
+ public void setSpeakerphoneOn(IBinder cb, boolean on,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource == null) {
+ return;
+ }
if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
return;
}
@@ -6914,8 +7106,8 @@ public class AudioService extends IAudioService.Stub
== PackageManager.PERMISSION_GRANTED;
// for logging only
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ final int uid = attributionSource.getUid();
+ final int pid = attributionSource.getPid();
final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
.append(") from u/pid:").append(uid).append("/")
@@ -6930,7 +7122,7 @@ public class AudioService extends IAudioService.Stub
final long ident = Binder.clearCallingIdentity();
try {
- mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource);
+ mDeviceBroker.setSpeakerphoneOn(cb, attributionSource, on, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -7034,13 +7226,17 @@ public class AudioService extends IAudioService.Stub
}
/** @see AudioManager#startBluetoothSco() */
- public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+ public void startBluetoothSco(IBinder cb, int targetSdkVersion,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource == null) {
+ return;
+ }
if (!checkAudioSettingsPermission("startBluetoothSco()")) {
return;
}
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ final int uid = attributionSource.getUid();
+ final int pid = attributionSource.getPid();
final int scoAudioMode =
(targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
@@ -7055,18 +7251,22 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(scoAudioMode))
.record();
- startBluetoothScoInt(cb, uid, scoAudioMode, eventSource);
+ startBluetoothScoInt(cb, attributionSource, scoAudioMode, eventSource);
}
/** @see AudioManager#startBluetoothScoVirtualCall() */
- public void startBluetoothScoVirtualCall(IBinder cb) {
+ public void startBluetoothScoVirtualCall(IBinder cb,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource == null) {
+ return;
+ }
if (!checkAudioSettingsPermission("startBluetoothScoVirtualCall()")) {
return;
}
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ final int uid = attributionSource.getUid();
+ final int pid = attributionSource.getPid();
final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
@@ -7078,10 +7278,11 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
.record();
- startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+ startBluetoothScoInt(cb, attributionSource, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
}
- void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) {
+ void startBluetoothScoInt(IBinder cb, AttributionSource attributionSource,
+ int scoAudioMode, @NonNull String eventSource) {
MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -7097,7 +7298,7 @@ public class AudioService extends IAudioService.Stub
final long ident = Binder.clearCallingIdentity();
try {
mDeviceBroker.startBluetoothScoForClient(
- cb, uid, scoAudioMode, isPrivileged, eventSource);
+ cb, attributionSource, scoAudioMode, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -7105,13 +7306,17 @@ public class AudioService extends IAudioService.Stub
}
/** @see AudioManager#stopBluetoothSco() */
- public void stopBluetoothSco(IBinder cb){
+ public void stopBluetoothSco(IBinder cb,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource == null) {
+ return;
+ }
if (!checkAudioSettingsPermission("stopBluetoothSco()") ||
!mSystemReady) {
return;
}
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ final int uid = attributionSource.getUid();
+ final int pid = attributionSource.getPid();
final String eventSource = new StringBuilder("stopBluetoothSco()")
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
@@ -7119,7 +7324,8 @@ public class AudioService extends IAudioService.Stub
== PackageManager.PERMISSION_GRANTED;
final long ident = Binder.clearCallingIdentity();
try {
- mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource);
+ mDeviceBroker.stopBluetoothScoForClient(
+ cb, attributionSource, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -8020,7 +8226,14 @@ public class AudioService extends IAudioService.Stub
}
synchronized (mAbsoluteVolumeDeviceInfoMapLock) {
if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) {
- return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior;
+ final AbsoluteVolumeDeviceInfo deviceInfo = mAbsoluteVolumeDeviceInfoMap.get(
+ audioSystemDeviceOut);
+ if (deviceInfo != null) {
+ return deviceInfo.mDeviceVolumeBehavior;
+ }
+
+ Log.e(TAG,
+ "Null absolute volume device info stored for key " + audioSystemDeviceOut);
}
}
@@ -8637,9 +8850,14 @@ public class AudioService extends IAudioService.Stub
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
// index is just set to 0 to repect BT requirements
+ boolean muted = false;
if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
&& getVssForStreamOrDefault(mPublicStreamType).isFullyMuted()) {
- index = 0;
+ if (ringMyCar()) {
+ muted = true;
+ } else {
+ index = 0;
+ }
} else if (isStreamBluetoothSco(mPublicStreamType) && index == 0) {
index = 1;
}
@@ -8649,13 +8867,14 @@ public class AudioService extends IAudioService.Stub
/ getVssForStreamOrDefault(mPublicStreamType).getIndexStepFactor());
}
+
if (DEBUG_VOL) {
Log.d(TAG, "setVolumeIndexInt(" + mAudioVolumeGroup.getId() + ", " + index + ", "
- + device + ")");
+ + muted + ", " + device + ")");
}
// Set the volume index
- mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+ mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, muted, device);
}
@GuardedBy("AudioService.VolumeStreamState.class")
@@ -8988,16 +9207,6 @@ public class AudioService extends IAudioService.Stub
mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
- final int status = AudioSystem.initStreamVolume(
- streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
- if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.enqueue(new EventLogger.StringEvent(
- "VSS() stream:" + streamType + " initStreamVolume=" + status)
- .printLog(ALOGE, TAG));
- sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
- "VSS()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
- }
-
updateIndexFactors();
mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
@@ -9029,6 +9238,9 @@ public class AudioService extends IAudioService.Stub
return;
}
+ // index values sent to APM are in the stream type SDK range, not *10
+ int indexMinVolCurve = MIN_STREAM_VOLUME[mStreamType];
+ int indexMaxVolCurve = MAX_STREAM_VOLUME[mStreamType];
synchronized (this) {
if (mStreamType == AudioSystem.STREAM_VOICE_CALL) {
if (MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]
@@ -9039,11 +9251,15 @@ public class AudioService extends IAudioService.Stub
if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
// SCO devices have a different min index
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+ indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
mIndexStepFactor = 1.f;
} else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
// For non SCO devices the stream state does not change the min index
if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+ indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
} else {
mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
}
@@ -9062,6 +9278,19 @@ public class AudioService extends IAudioService.Stub
mIndexMinNoPerm = mIndexMin;
}
}
+
+ final int status = AudioSystem.initStreamVolume(
+ mStreamType, indexMinVolCurve, indexMaxVolCurve);
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(
+ "updateIndexFactors() stream:" + mStreamType + " index min/max:"
+ + mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
+ + mIndexStepFactor).printSlog(ALOGI, TAG));
+ if (status != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(
+ "Failed initStreamVolume with status=" + status).printSlog(ALOGE, TAG));
+ sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+ "updateIndexFactors()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+ }
}
/**
@@ -9239,6 +9468,13 @@ public class AudioService extends IAudioService.Stub
}
}
+ /**
+ * Sends the new volume index on the given device to native.
+ *
+ * <p>Make sure the index is consistent with the muting state. When ringMyCar is enabled
+ * will send the non-zero index together with muted state. Otherwise, index 0 will be sent
+ * to native for signalising a muted stream.
+ **/
@GuardedBy("VolumeStreamState.class")
private void setStreamVolumeIndex(int index, int device) {
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
@@ -9253,18 +9489,19 @@ public class AudioService extends IAudioService.Stub
/ 10;
}
+ boolean muted = ringMyCar() ? isFullyMuted() : false;
if (DEBUG_VOL) {
- Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device
- + ")");
+ Log.d(TAG, "setStreamVolumeIndexAS(streamType=" + mStreamType + ", index=" + index
+ + ", muted=" + muted + ", device=" + device + ")");
}
- mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
+ mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, muted, device);
}
// must be called while synchronized VolumeStreamState.class
@GuardedBy("VolumeStreamState.class")
/*package*/ void applyDeviceVolume_syncVSS(int device) {
int index;
- if (isFullyMuted()) {
+ if (isFullyMuted() && !ringMyCar()) {
index = 0;
} else if (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
@@ -9298,7 +9535,7 @@ public class AudioService extends IAudioService.Stub
for (int i = 0; i < mIndexMap.size(); i++) {
final int device = mIndexMap.keyAt(i);
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
- if (isFullyMuted()) {
+ if (isFullyMuted() && !ringMyCar()) {
index = 0;
} else if (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
@@ -9324,16 +9561,18 @@ public class AudioService extends IAudioService.Stub
index = (mIndexMap.valueAt(i) + 5)/10;
}
- sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
- SENDMSG_REPLACE, device, isAbsoluteVolume ? 1 : 0, this,
- /*delay=*/0);
+ if (mStreamType == AudioSystem.STREAM_MUSIC) {
+ sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
+ SENDMSG_QUEUE, device, isAbsoluteVolume ? 1 : 0, this,
+ /*delay=*/0);
+ }
setStreamVolumeIndex(index, device);
}
}
// apply default volume last: by convention , default device volume will be used
// by audio policy manager if no explicit volume is present for a given device type
- if (isFullyMuted()) {
+ if (isFullyMuted() && !ringMyCar()) {
index = 0;
} else {
index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
@@ -9431,16 +9670,9 @@ public class AudioService extends IAudioService.Stub
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
oldIndex);
- int extraStreamType = mStreamType;
- // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO
- if (isStreamBluetoothSco(mStreamType)) {
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- AudioSystem.STREAM_BLUETOOTH_SCO);
- extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO;
- } else {
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- mStreamType);
- }
+
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ mStreamType);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
streamAlias);
@@ -9451,21 +9683,9 @@ public class AudioService extends IAudioService.Stub
" aliased streams: " + aliasStreamIndexes;
}
AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- extraStreamType, aliasStreamIndexesString, index, oldIndex));
- if (extraStreamType != mStreamType) {
- AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- mStreamType, aliasStreamIndexesString, index, oldIndex));
- }
+ mStreamType, aliasStreamIndexesString, index, oldIndex));
}
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
- if (extraStreamType != mStreamType) {
- // send multiple intents in case we merged voice call and bt sco streams
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- mStreamType);
- // do not use the options in thid case which could discard
- // the previous intent
- sendBroadcastToAll(mVolumeChanged, null);
- }
}
}
}
@@ -9873,7 +10093,7 @@ public class AudioService extends IAudioService.Stub
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
- sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_REPLACE,
+ sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_QUEUE,
device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device) ? 1 : 0),
streamState, /*delay=*/0);
@@ -9999,6 +10219,14 @@ public class AudioService extends IAudioService.Stub
vgs.persistVolumeGroup(msg.arg1);
break;
+ case MSG_APPLY_INPUT_GAIN_INDEX:
+ onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+ break;
+
+ case MSG_PERSIST_INPUT_GAIN_INDEX:
+ onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
+ break;
+
case MSG_PERSIST_RINGER_MODE:
// note that the value persisted is the current ringer mode, not the
// value of ringer mode as of the time the request was made to persist
@@ -10069,6 +10297,11 @@ public class AudioService extends IAudioService.Stub
mAudioEventWakeLock.release();
break;
+ case MSG_INIT_INPUT_GAINS:
+ onInitInputGains();
+ mAudioEventWakeLock.release();
+ break;
+
case MSG_INIT_ADI_DEVICE_STATES:
onInitAdiDeviceStates();
mAudioEventWakeLock.release();
@@ -10162,7 +10395,7 @@ public class AudioService extends IAudioService.Stub
h.setRecordingActive(isRecordingActiveForUid(h.getUid()));
if (wasActive != h.isActive()) {
onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
- mContext.getPackageName(), false /*force*/);
+ mContext.getPackageName(), false /*force*/, false /*signal*/);
}
}
break;
@@ -10191,8 +10424,11 @@ public class AudioService extends IAudioService.Stub
break;
case MSG_UPDATE_AUDIO_MODE:
+ case MSG_UPDATE_AUDIO_MODE_SIGNAL:
synchronized (mDeviceBroker.mSetModeLock) {
- onUpdateAudioMode(msg.arg1, msg.arg2, (String) msg.obj, false /*force*/);
+ UpdateAudioModeInfo info = (UpdateAudioModeInfo) msg.obj;
+ onUpdateAudioMode(info.getMode(), info.getPid(), info.getPackageName(),
+ false /*force*/, msg.what == MSG_UPDATE_AUDIO_MODE_SIGNAL);
}
break;
@@ -10357,14 +10593,23 @@ public class AudioService extends IAudioService.Stub
}
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) {
+ Log.i(TAG, "setAvrcpAbsoluteVolumeSupported support " + support);
synchronized (mCachedAbsVolDrivingStreamsLock) {
mAvrcpAbsVolSupported = support;
if (absVolumeIndexFix()) {
int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
if (!mAvrcpAbsVolSupported) {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
- "", /*enabled*/false, AudioSystem.STREAM_DEFAULT);
+ final int result = mAudioSystem.setDeviceAbsoluteVolumeEnabled(
+ a2dpDev, /*address=*/"", /*enabled*/false,
+ AudioSystem.STREAM_DEFAULT);
+ if (result != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueueAndSlog(
+ new VolumeEvent(VolumeEvent.VOL_ABS_DEVICE_ENABLED_ERROR,
+ result, a2dpDev, /*enabled=*/false,
+ AudioSystem.STREAM_DEFAULT).eventToString(), ALOGE,
+ TAG);
+ }
return null;
}
// For A2DP and AVRCP we need to set the driving stream based on the
@@ -10372,8 +10617,14 @@ public class AudioService extends IAudioService.Stub
// and setStreamVolume that the driving abs volume stream is consistent.
int streamToDriveAbs = getBluetoothContextualVolumeStream();
if (stream == null || stream != streamToDriveAbs) {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
- "", /*enabled*/true, streamToDriveAbs);
+ final int result = mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev,
+ /*address=*/"", /*enabled*/true, streamToDriveAbs);
+ if (result != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueueAndSlog(
+ new VolumeEvent(VolumeEvent.VOL_ABS_DEVICE_ENABLED_ERROR,
+ result, a2dpDev, /*enabled=*/true,
+ streamToDriveAbs).eventToString(), ALOGE, TAG);
+ }
}
return streamToDriveAbs;
});
@@ -10678,14 +10929,122 @@ public class AudioService extends IAudioService.Stub
}
};
mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange(
- PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
task);
} else {
mAudioSystem.listenForSystemPropertyChange(
- PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
() -> mAudioServerLifecycleExecutor.execute(
mPermissionProvider::onPermissionStateChanged));
}
+
+ if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
+ new PackageInfoTransducer().start();
+ }
+ }
+
+ /**
+ * A transducer that converts high-speed changes in the CACHE_KEY_PACKAGE_INFO_CACHE
+ * PropertyInvalidatedCache into low-speed changes in the CACHE_KEY_PACKAGE_INFO_NOTIFY system
+ * property. This operates on the popcorn principle: changes in the source are done when the
+ * source has been quiet for the soak interval.
+ *
+ * TODO(b/381097912) This is a temporary measure to support migration away from sysprop
+ * sniffing. It should be cleaned up.
+ */
+ private static class PackageInfoTransducer extends Thread {
+
+ // The run/stop signal.
+ private final AtomicBoolean mRunning = new AtomicBoolean(false);
+
+ // The source of change information.
+ private final PropertyInvalidatedCache.NonceWatcher mWatcher;
+
+ // The handler for scheduling delayed reactions to changes.
+ private final Handler mHandler;
+
+ // How long to soak changes: 50ms is the legacy choice.
+ private final static long SOAK_TIME_MS = 50;
+
+ // The ubiquitous lock.
+ private final Object mLock = new Object();
+
+ // If positive, this is the soak expiration time.
+ @GuardedBy("mLock")
+ private long mSoakDeadlineMs = -1;
+
+ // A source of unique long values.
+ @GuardedBy("mLock")
+ private long mToken = 0;
+
+ PackageInfoTransducer() {
+ mWatcher = PropertyInvalidatedCache
+ .getNonceWatcher(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
+ mHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ PackageInfoTransducer.this.handleMessage(msg);
+ }};
+ }
+
+ public void run() {
+ mRunning.set(true);
+ while (mRunning.get()) {
+ try {
+ final int changes = mWatcher.waitForChange();
+ if (changes == 0 || !mRunning.get()) {
+ continue;
+ }
+ } catch (InterruptedException e) {
+ // We don't know why the exception occurred but keep running until told to
+ // stop.
+ continue;
+ }
+ trigger();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updateLocked() {
+ String n = Long.toString(mToken++);
+ SystemProperties.set(PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, n);
+ }
+
+ private void trigger() {
+ synchronized (mLock) {
+ boolean alreadyQueued = mSoakDeadlineMs >= 0;
+ final long nowMs = SystemClock.uptimeMillis();
+ mSoakDeadlineMs = nowMs + SOAK_TIME_MS;
+ if (!alreadyQueued) {
+ mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs);
+ updateLocked();
+ }
+ }
+ }
+
+ private void handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (mSoakDeadlineMs < 0) {
+ return; // ???
+ }
+ final long nowMs = SystemClock.uptimeMillis();
+ if (mSoakDeadlineMs > nowMs) {
+ mSoakDeadlineMs = nowMs + SOAK_TIME_MS;
+ mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs);
+ return;
+ }
+ mSoakDeadlineMs = -1;
+ updateLocked();
+ }
+ }
+
+ /**
+ * Cause the thread to exit. Running is set to false and the watcher is awakened.
+ */
+ public void done() {
+ mRunning.set(false);
+ mWatcher.wakeUp();
+ }
}
//==========================================================================================
@@ -10895,9 +11254,68 @@ public class AudioService extends IAudioService.Stub
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
mmi.record();
+ //delay abandon focus requests from Telecom if an audio mode reset from Telecom
+ // is still being processed
+ final boolean abandonFromTelecom = (mContext.checkCallingOrSelfPermission(
+ MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED)
+ && ((aa != null && aa.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ || AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId));
+ if (abandonFromTelecom) {
+ synchronized (mAudioModeResetLock) {
+ final long start = java.lang.System.currentTimeMillis();
+ long elapsed = 0;
+ while (mAudioModeResetCount > 0) {
+ if (DEBUG_MODE) {
+ Log.i(TAG, "Abandon focus from Telecom, waiting for mode change");
+ }
+ try {
+ mAudioModeResetLock.wait(
+ AUDIO_MODE_RESET_TIMEOUT_MS - elapsed);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting for audio mode reset");
+ }
+ elapsed = java.lang.System.currentTimeMillis() - start;
+ if (elapsed >= AUDIO_MODE_RESET_TIMEOUT_MS) {
+ Log.e(TAG, "Timeout waiting for audio mode reset");
+ // reset count to avoid sticky out of sync state.
+ resetAudioModeResetCount();
+ break;
+ }
+ }
+ if (DEBUG_MODE && elapsed != 0) {
+ Log.i(TAG, "Abandon focus from Telecom done waiting");
+ }
+ }
+ }
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
}
+ /** synchronization between setMode(NORMAL) and abandonAudioFocus() from Telecom */
+ private static final long AUDIO_MODE_RESET_TIMEOUT_MS = 3000;
+
+ private final Object mAudioModeResetLock = new Object();
+
+ @GuardedBy("mAudioModeResetLock")
+ private int mAudioModeResetCount = 0;
+
+ void decrementAudioModeResetCount() {
+ synchronized (mAudioModeResetLock) {
+ if (mAudioModeResetCount > 0) {
+ mAudioModeResetCount--;
+ } else {
+ Log.w(TAG, "mAudioModeResetCount already 0");
+ }
+ mAudioModeResetLock.notify();
+ }
+ }
+
+ private void resetAudioModeResetCount() {
+ synchronized (mAudioModeResetLock) {
+ mAudioModeResetCount = 0;
+ mAudioModeResetLock.notify();
+ }
+ }
+
/** see {@link AudioManager#abandonAudioFocusForTest(AudioFocusRequest, String)} */
public int abandonAudioFocusForTest(IAudioFocusDispatcher fd, String clientId,
AudioAttributes aa, String callingPackageName) {
@@ -11109,6 +11527,10 @@ public class AudioService extends IAudioService.Stub
return mSpatializerHelper.canBeSpatialized(attributes, format);
}
+ public @NonNull List<Integer> getSpatializedChannelMasks() {
+ return mSpatializerHelper.getSpatializedChannelMasks();
+ }
+
/** @see Spatializer.SpatializerInfoDispatcherStub */
public void registerSpatializerCallback(
@NonNull ISpatializerCallback cb) {
@@ -12423,7 +12845,7 @@ public class AudioService extends IAudioService.Stub
pw.println("\nLoudness alignment:");
mLoudnessCodecHelper.dump(pw);
- pw.println("\nAbsolute voume devices:");
+ pw.println("\nAbsolute volume devices with their volume driving streams:");
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.forEach((dev, stream) -> pw.println(
"Device type: 0x" + Integer.toHexString(dev) + ", driving stream " + stream));
@@ -13853,10 +14275,10 @@ public class AudioService extends IAudioService.Stub
* Update player event
* @param piid Player id to update
* @param event The new player event
- * @param eventValue The value associated with this event
+ * @param eventValues The values associated with this event
*/
- public void playerEvent(int piid, int event, int eventValue) {
- mPlaybackMonitor.playerEvent(piid, event, eventValue, Binder.getCallingUid());
+ public void playerEvent(int piid, int event, int[] eventValues) {
+ mPlaybackMonitor.playerEvent(piid, event, eventValues, Binder.getCallingUid());
}
/**
@@ -14967,6 +15389,11 @@ public class AudioService extends IAudioService.Stub
private void addAudioSystemDeviceOutToAbsVolumeDevices(int audioSystemDeviceOut,
AbsoluteVolumeDeviceInfo info) {
+ if (info == null) {
+ Log.e(TAG, "Cannot add null absolute volume info for audioSystemDeviceOut "
+ + audioSystemDeviceOut);
+ return;
+ }
if (DEBUG_VOL) {
Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
+ " to mAbsoluteVolumeDeviceInfoMap with behavior "
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 631d5d80acd7..1b5f0e5d31c8 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -234,6 +234,7 @@ public class AudioServiceEvents {
static final int VOL_SET_LE_AUDIO_VOL = 10;
static final int VOL_ADJUST_GROUP_VOL = 11;
static final int VOL_MASTER_MUTE = 12;
+ static final int VOL_ABS_DEVICE_ENABLED_ERROR = 13;
final int mOp;
final int mStream;
@@ -365,6 +366,19 @@ public class AudioServiceEvents {
logMetricEvent();
}
+ /** used for VOL_ABS_DEVICE_ENABLED_ERROR */
+ VolumeEvent(int op, int result, int device, boolean enabled, int streamType) {
+ mOp = op;
+ mStream = streamType;
+ mVal1 = device;
+ mVal2 = enabled ? 1 : 0;
+ mVal3 = result;
+ // unused
+ mCaller = null;
+ mGroupName = null;
+ logMetricEvent();
+ }
+
/**
* Audio Analytics unique Id.
@@ -481,6 +495,9 @@ public class AudioServiceEvents {
case VOL_MASTER_MUTE:
// No value in logging metrics for this internal event
return;
+ case VOL_ABS_DEVICE_ENABLED_ERROR:
+ // No value in logging metrics for this internal event
+ return;
default:
return;
}
@@ -569,6 +586,13 @@ public class AudioServiceEvents {
return new StringBuilder("Master mute:")
.append(mVal1 == 1 ? " muted)" : " unmuted)")
.toString();
+ case VOL_ABS_DEVICE_ENABLED_ERROR:
+ return new StringBuilder("setDeviceAbsoluteVolumeEnabled failed with ")
+ .append(mVal3)
+ .append(" for dev: 0x").append(Integer.toHexString(mVal1))
+ .append(" enabled: ").append(mVal2)
+ .append(" streamType: ").append(mStream)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}
@@ -633,8 +657,8 @@ public class AudioServiceEvents {
return "CSD lowering volume to RS1";
case UPDATE_ABS_VOLUME_ATTENUATION:
return String.format(java.util.Locale.US,
- "Updating CSD absolute volume attenuation on device %d with %.2f dB ",
- mLongValue, mFloatValue);
+ "Updating CSD absolute volume attenuation on device 0x%s with %.2f dB ",
+ Long.toHexString(mLongValue), mFloatValue);
}
return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 5cabddea9c17..e86c34cab88a 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -547,13 +547,14 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
* @param device
* @return
*/
- public int setStreamVolumeIndexAS(int stream, int index, int device) {
- return AudioSystem.setStreamVolumeIndexAS(stream, index, device);
+ public int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) {
+ return AudioSystem.setStreamVolumeIndexAS(stream, index, muted, device);
}
/** Same as {@link AudioSystem#setVolumeIndexForAttributes(AudioAttributes, int, int)} */
- public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) {
- return AudioSystem.setVolumeIndexForAttributes(attributes, index, device);
+ public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, boolean muted,
+ int device) {
+ return AudioSystem.setVolumeIndexForAttributes(attributes, index, muted, device);
}
/**
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index f462539d5bbf..5ebe6a1553b9 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -16,6 +16,9 @@
package com.android.server.audio;
+import static com.android.server.utils.EventLogger.Event.ALOGE;
+import static com.android.server.utils.EventLogger.Event.ALOGW;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.UserProperties;
@@ -24,6 +27,7 @@ import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.IAudioFocusDispatcher;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -31,6 +35,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.List;
@@ -84,6 +89,8 @@ public class FocusRequester {
*/
private final @NonNull AudioAttributes mAttributes;
+ private final EventLogger mEventLogger;
+
/**
* Class constructor
* @param aa
@@ -100,7 +107,7 @@ public class FocusRequester {
FocusRequester(@NonNull AudioAttributes aa, int focusRequest, int grantFlags,
IAudioFocusDispatcher afl, IBinder source, @NonNull String id,
AudioFocusDeathHandler hdlr, @NonNull String pn, int uid,
- @NonNull MediaFocusControl ctlr, int sdk) {
+ @NonNull MediaFocusControl ctlr, int sdk, EventLogger eventLogger) {
mAttributes = aa;
mFocusDispatcher = afl;
mSourceRef = source;
@@ -115,10 +122,12 @@ public class FocusRequester {
mFocusLossFadeLimbo = false;
mFocusController = ctlr;
mSdkTarget = sdk;
+ mEventLogger = eventLogger;
}
FocusRequester(AudioFocusInfo afi, IAudioFocusDispatcher afl,
- IBinder source, AudioFocusDeathHandler hdlr, @NonNull MediaFocusControl ctlr) {
+ IBinder source, AudioFocusDeathHandler hdlr, @NonNull MediaFocusControl ctlr,
+ EventLogger eventLogger) {
mAttributes = afi.getAttributes();
mClientId = afi.getClientId();
mPackageName = afi.getPackageName();
@@ -134,6 +143,7 @@ public class FocusRequester {
mSourceRef = source;
mDeathHandler = hdlr;
mFocusController = ctlr;
+ mEventLogger = eventLogger;
}
boolean hasSameClient(String otherClient) {
@@ -357,18 +367,22 @@ public class FocusRequester {
mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
final IAudioFocusDispatcher fd = mFocusDispatcher;
- if (fd != null) {
+ if (fd != null && mFocusLossWasNotified) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ mClientId);
}
- if (mFocusLossWasNotified) {
- fd.dispatchAudioFocusChange(focusGain, mClientId);
- }
+ fd.dispatchAudioFocusChange(focusGain, mClientId);
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusGain, "handleGain"));
+ } else if (mFocusLossWasNotified) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusGain, "handleGain no listener").printSlog(ALOGW, TAG));
}
mFocusController.restoreVShapedPlayers(this);
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
+ } catch (RemoteException e) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusGain, "handleGain exc: " + e).printSlog(ALOGE, TAG));
}
}
@@ -385,62 +399,67 @@ public class FocusRequester {
if (DEBUG) {
Log.i(TAG, "handleFocusLoss for " + mClientId + " loss:" + focusLoss);
}
- try {
- if (focusLoss != mFocusLossReceived) {
- mFocusLossReceived = focusLoss;
- mFocusLossWasNotified = false;
- // before dispatching a focus loss, check if the following conditions are met:
- // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
- // (i.e. it has a focus controller that implements a ducking policy)
- // 2/ it is a DUCK loss
- // 3/ the focus loser isn't flagged as pausing in a DUCK loss
- // if they are, do not notify the focus loser
- if (!mFocusController.mustNotifyFocusOwnerOnDuck()
- && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
- && (mGrantFlags
- & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
- if (DEBUG) {
- Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + ", to be handled externally");
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), false /* wasDispatched */);
- return;
+ if (focusLoss != mFocusLossReceived) {
+ mFocusLossReceived = focusLoss;
+ mFocusLossWasNotified = false;
+ // before dispatching a focus loss, check if the following conditions are met:
+ // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
+ // (i.e. it has a focus controller that implements a ducking policy)
+ // 2/ it is a DUCK loss
+ // 3/ the focus loser isn't flagged as pausing in a DUCK loss
+ // if they are, do not notify the focus loser
+ if (!mFocusController.mustNotifyFocusOwnerOnDuck()
+ && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+ && (mGrantFlags
+ & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
+ if (DEBUG) {
+ Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + ", to be handled externally");
}
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), false /* wasDispatched */);
+ return;
+ }
- // check enforcement by the framework
- boolean handled = false;
- if (frWinner != null) {
- handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
- }
+ // check enforcement by the framework
+ boolean handled = false;
+ if (frWinner != null) {
+ handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
+ }
- if (handled) {
- if (DEBUG) {
- Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + ", response handled by framework");
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), false /* wasDispatched */);
- return; // with mFocusLossWasNotified = false
+ if (handled) {
+ if (DEBUG) {
+ Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + ", response handled by framework");
}
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), false /* wasDispatched */);
+ return; // with mFocusLossWasNotified = false
+ }
- final IAudioFocusDispatcher fd = mFocusDispatcher;
- if (fd != null) {
- if (DEBUG) {
- Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
- + mClientId);
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), true /* wasDispatched */);
- mFocusLossWasNotified = true;
+ final IAudioFocusDispatcher fd = mFocusDispatcher;
+ if (fd != null) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ + mClientId);
+ }
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), true /* wasDispatched */);
+ mFocusLossWasNotified = true;
+ try {
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
- } else if (DEBUG) {
- Log.i(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + " no IAudioFocusDispatcher");
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, mFocusLossReceived, "handleLoss"));
+ } catch (RemoteException e) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, mFocusLossReceived, "handleLoss failed exc: " + e)
+ .printSlog(ALOGE,TAG));
}
+ } else {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, mFocusLossReceived, "handleLoss failed no listener")
+ .printSlog(ALOGE, TAG));
}
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
}
}
@@ -505,7 +524,7 @@ public class FocusRequester {
return false;
}
- int dispatchFocusChange(int focusChange) {
+ int dispatchFocusChange(int focusChange, String reason) {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); }
@@ -528,8 +547,11 @@ public class FocusRequester {
}
try {
fd.dispatchAudioFocusChange(focusChange, mClientId);
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e);
+ mEventLogger.enqueue(new FocusRequestEvent(this,
+ focusChange, "dispatch: " + reason));
+ } catch (RemoteException e) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusChange, "dispatch failed: " + e).printSlog(ALOGE, TAG));
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
@@ -559,7 +581,7 @@ public class FocusRequester {
}
}
}
- return dispatchFocusChange(focusChange);
+ return dispatchFocusChange(focusChange, "focus with fade");
}
void dispatchFocusResultFromExtPolicy(int requestResult) {
@@ -575,7 +597,7 @@ public class FocusRequester {
}
try {
fd.dispatchFocusResultFromExtPolicy(requestResult, mClientId);
- } catch (android.os.RemoteException e) {
+ } catch (RemoteException e) {
Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener"
+ mClientId, e);
}
@@ -585,4 +607,32 @@ public class FocusRequester {
return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget);
}
+
+ static class FocusRequestEvent extends EventLogger.Event {
+ private final String mClientId;
+ private final int mUid;
+ private final String mPackageName;
+ private final int mCode;
+ private final String mDescription;
+
+ public FocusRequestEvent(FocusRequester fr, String description) {
+ this(fr, -1, description);
+ }
+
+ public FocusRequestEvent(FocusRequester fr, int code, String description) {
+ mClientId = fr.getClientId();
+ mUid = fr.getClientUid();
+ mPackageName = fr.getPackageName();
+ mCode = code;
+ mDescription = description != null ? description : "";
+ }
+ @Override
+ public String eventToString() {
+ return "focus owner: " + mClientId + " in uid: " + mUid
+ + " pack: " + mPackageName
+ + ((mCode != -1) ? " code: " + mCode : "")
+ + " event: " + mDescription;
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
new file mode 100644
index 000000000000..d7d1ac96d650
--- /dev/null
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import static android.media.AudioManager.GET_DEVICES_INPUTS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioSystem;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/** Maintains the current state of input gains. */
+/*package*/ class InputDeviceVolumeHelper {
+ private static final String TAG = "InputDeviceVolumeHelper";
+
+ // TODO(b/364923030): retrieve these constants from AudioPolicyManager.
+ private final int INDEX_MIN = 0;
+ private final int INDEX_MAX = 100;
+ private final int INDEX_DEFAULT = 50;
+
+ private final SettingsAdapter mSettings;
+ private final ContentResolver mContentResolver;
+ private final String mInputGainIndexSettingsName;
+
+ // A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
+ // index.
+ private final SparseIntArray mInputGainIndexMap;
+ private final Set<Integer> mSupportedDeviceTypes = new HashSet<>();
+
+ InputDeviceVolumeHelper(
+ SettingsAdapter settings,
+ ContentResolver contentResolver,
+ String settingsName) {
+ mSettings = settings;
+ mContentResolver = contentResolver;
+ mInputGainIndexSettingsName = settingsName;
+
+ IntArray internalDeviceTypes = new IntArray();
+ int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:"
+ + status);
+ }
+
+ // Note that in a rare case, if AudioSystem.getSupportedDeviceTypes call fails, both
+ // mInputGainIndexMap and mSupportedDeviceTypes will be empty.
+ mInputGainIndexMap = new SparseIntArray(internalDeviceTypes.size());
+ for (int i = 0; i < internalDeviceTypes.size(); i++) {
+ mSupportedDeviceTypes.add(internalDeviceTypes.get(i));
+ }
+
+ readSettings();
+ }
+
+ private void readSettings() {
+ synchronized (InputDeviceVolumeHelper.class) {
+ for (int inputDeviceType : mSupportedDeviceTypes) {
+ // Retrieve current input gain for device. If no input gain stored for current
+ // device, use default input gain.
+ String name = getSettingNameForDevice(inputDeviceType);
+ int index = name == null
+ ? INDEX_DEFAULT
+ : mSettings.getSystemIntForUser(
+ mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
+
+ mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
+ }
+ }
+ }
+
+ private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+ if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
+ return null;
+ }
+
+ final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
+ if (suffix.isEmpty()) {
+ return mInputGainIndexSettingsName;
+ }
+ return mInputGainIndexSettingsName + "_" + suffix;
+ }
+
+ private int getValidIndex(int index) {
+ if (index < INDEX_MIN) {
+ return INDEX_MIN;
+ }
+ if (index > INDEX_MAX) {
+ return INDEX_MAX;
+ }
+ return index;
+ }
+
+ public int getInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
+ ensureValidInputDeviceType(inputDeviceType);
+
+ synchronized (InputDeviceVolumeHelper.class) {
+ return mInputGainIndexMap.get(inputDeviceType, INDEX_DEFAULT);
+ }
+ }
+
+ public int getMaxInputGainIndex() {
+ return INDEX_MAX;
+ }
+
+ public int getMinInputGainIndex() {
+ return INDEX_MIN;
+ }
+
+ public boolean isInputGainFixed(@NonNull AudioDeviceAttributes ada) {
+ int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
+ ensureValidInputDeviceType(inputDeviceType);
+
+ // For simplicity, all devices have non fixed input gain. This might change
+ // when more input devices are supported and some do not support input gain control.
+ return false;
+ }
+
+ public boolean setInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+ int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
+ ensureValidInputDeviceType(inputDeviceType);
+
+ int oldIndex;
+ synchronized (InputDeviceVolumeHelper.class) {
+ oldIndex = getInputGainIndex(ada);
+ index = getValidIndex(index);
+
+ if (oldIndex == index) {
+ return false;
+ }
+
+ mInputGainIndexMap.put(inputDeviceType, index);
+ return true;
+ }
+ }
+
+ public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
+ String name = getSettingNameForDevice(inputDeviceType);
+ if (name != null) {
+ int index = getInputGainIndex(ada);
+ mSettings.putSystemIntForUser(
+ mContentResolver,
+ name,
+ index,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ public boolean isValidInputDeviceType(int inputDeviceType) {
+ return mSupportedDeviceTypes.contains(inputDeviceType);
+ }
+
+ private void ensureValidInputDeviceType(int inputDeviceType) {
+ if (!isValidInputDeviceType(inputDeviceType)) {
+ throw new IllegalArgumentException("Bad input device type " + inputDeviceType);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index b4af46efcb38..1604e94e5a6d 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -231,13 +231,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
final FocusRequester focusOwner = stackIterator.next();
if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
clientsToRemove.add(focusOwner.getClientId());
- mEventLogger.enqueue((new EventLogger.StringEvent(
- "focus owner:" + focusOwner.getClientId()
- + " in uid:" + uid + " pack: " + packageName
- + " getting AUDIOFOCUS_LOSS due to app suspension"))
- .printLog(TAG));
// make the suspended app lose focus through its focus listener (if any)
- focusOwner.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
+ focusOwner.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS, "app suspension");
}
}
for (String clientToRemove : clientsToRemove) {
@@ -548,11 +543,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
FocusRequester fr = stackIterator.next();
if(fr.hasSameBinder(cb)) {
Log.i(TAG, "AudioFocus removeFocusStackEntryOnDeath(): removing entry for " + cb);
- mEventLogger.enqueue(new EventLogger.StringEvent(
- "focus requester:" + fr.getClientId()
- + " in uid:" + fr.getClientUid()
- + " pack:" + fr.getPackageName()
- + " died"));
+ mEventLogger.enqueue(new FocusRequester.FocusRequestEvent(fr, " died"));
notifyExtPolicyFocusLoss_syncAf(fr.toAudioFocusInfo(), false);
stackIterator.remove();
@@ -585,11 +576,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
final FocusRequester fr = owner.getValue();
if (fr.hasSameBinder(cb)) {
ownerIterator.remove();
- mEventLogger.enqueue(new EventLogger.StringEvent(
- "focus requester:" + fr.getClientId()
- + " in uid:" + fr.getClientUid()
- + " pack:" + fr.getPackageName()
- + " died"));
+ mEventLogger.enqueue(new FocusRequester.FocusRequestEvent(fr, "died"));
fr.release();
notifyExtFocusPolicyFocusAbandon_syncAf(fr.toAudioFocusInfo());
break;
@@ -900,7 +887,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
// new focus (future) focus owner to keep track of
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
- new FocusRequester(afi, fd, cb, hdlr, this));
+ new FocusRequester(afi, fd, cb, hdlr, this, mEventLogger));
}
try {
@@ -972,7 +959,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- return fr.dispatchFocusChange(focusChange);
+ return fr.dispatchFocusChange(focusChange, "audiomanager");
}
}
@@ -1006,6 +993,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
otherActiveFrs.add(otherFr);
}
+ // TODO log
int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs);
if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED
&& focusChange == AudioManager.AUDIOFOCUS_LOSS) {
@@ -1080,6 +1068,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
switch (attr.getUsage()) {
case AudioAttributes.USAGE_MEDIA:
case AudioAttributes.USAGE_GAME:
+ case AudioAttributes.USAGE_SPEAKER_CLEANUP:
return 1000;
case AudioAttributes.USAGE_ALARM:
case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
@@ -1260,7 +1249,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
- clientId, afdh, callingPackageName, uid, this, sdk);
+ clientId, afdh, callingPackageName, uid, this, sdk, mEventLogger);
if (mMultiAudioFocusEnabled
&& (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
@@ -1594,7 +1583,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
synchronized (mAudioFocusLock) {
final FocusRequester loser = (FocusRequester) msg.obj;
if (loser.isInFocusLossLimbo()) {
- loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
+ loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS, "loss after fade");
loser.release();
postForgetUidLater(loser);
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 88268cd19a38..1c01fb9f19e0 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,12 +17,14 @@
package com.android.server.audio;
import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_OP_PLAY_AUDIO;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_OP_CONTROL_AUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
@@ -72,6 +74,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
@@ -160,18 +163,22 @@ public final class PlaybackActivityMonitor
private final Context mContext;
private int mSavedAlarmVolume = -1;
+ private boolean mSavedAlarmMuted = false;
+ private final Function<Integer, Boolean> mIsStreamMutedCb;
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
private final FadeOutManager mFadeOutManager = new FadeOutManager();
PlaybackActivityMonitor(Context context, int maxAlarmVolume,
- Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
+ Consumer<AudioDeviceAttributes> muteTimeoutCallback,
+ Function<Integer, Boolean> isStreamMutedCb) {
mContext = context;
mMaxAlarmVolume = maxAlarmVolume;
PlayMonitorClient.sListenerDeathMonitor = this;
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
+ mIsStreamMutedCb = isStreamMutedCb;
initEventHandler();
}
@@ -253,7 +260,11 @@ public final class PlaybackActivityMonitor
updateAllowedCapturePolicy(apc, mAllowedCapturePolicies.get(uid));
}
}
- sEventLogger.enqueue(new NewPlayerEvent(apc));
+ var packages = mContext.getPackageManager().getPackagesForUid(apc.getClientUid());
+ sEventLogger.enqueue(new NewPlayerEvent(
+ apc,
+ packages != null && packages.length > 0 ? packages[0] : null
+ ));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
maybeMutePlayerAwaitingConnection(apc);
@@ -328,8 +339,9 @@ public final class PlaybackActivityMonitor
if (mPrivilegedAlarmActiveCount++ == 0) {
mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+ mSavedAlarmMuted = mIsStreamMutedCb.apply(AudioSystem.STREAM_ALARM);
AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
- mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+ mMaxAlarmVolume, /*muted=*/false, AudioSystem.DEVICE_OUT_SPEAKER);
}
} else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
@@ -338,7 +350,8 @@ public final class PlaybackActivityMonitor
AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
mMaxAlarmVolume) {
AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
- mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+ mSavedAlarmVolume, mSavedAlarmMuted,
+ AudioSystem.DEVICE_OUT_SPEAKER);
}
}
}
@@ -353,10 +366,11 @@ public final class PlaybackActivityMonitor
* @param eventValue The value associated with this event
* @param binderUid Calling binder uid
*/
- public void playerEvent(int piid, int event, int eventValue, int binderUid) {
+ public void playerEvent(int piid, int event, int[] eventValues, int binderUid) {
if (DEBUG) {
- Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
- piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
+ Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValues=%d)",
+ piid, AudioPlaybackConfiguration.playerStateToString(event),
+ Arrays.toString(eventValues)));
}
boolean change;
synchronized(mPlayerLock) {
@@ -370,13 +384,13 @@ public final class PlaybackActivityMonitor
// do not log nor dispatch events for "ignored" players other than the release
return;
}
- sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
+ sEventLogger.enqueue(new PlayerEvent(piid, event, eventValues));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
if (portToPiidSimplification()) {
- mPiidToPortId.put(piid, eventValue);
+ mPiidToPortId.put(piid, eventValues[0]);
} else {
- mPortIdToPiid.put(eventValue, piid);
+ mPortIdToPiid.put(eventValues[0], piid);
}
return;
} else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
@@ -397,7 +411,7 @@ public final class PlaybackActivityMonitor
if (checkConfigurationCaller(piid, apc, binderUid)) {
//TODO add generation counter to only update to the latest state
checkVolumeForPrivilegedAlarm(apc, event);
- change = apc.handleStateEvent(event, eventValue);
+ change = apc.handleStateEvent(event, eventValues);
} else {
Log.e(TAG, "Error handling event " + event);
change = false;
@@ -433,7 +447,7 @@ public final class PlaybackActivityMonitor
}
if (DEBUG) {
- Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
+ Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
}
@@ -505,7 +519,7 @@ public final class PlaybackActivityMonitor
mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid));
checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
- AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
+ AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID);
if (portToPiidSimplification()) {
mPiidToPortId.delete(piid);
@@ -1324,12 +1338,12 @@ public final class PlaybackActivityMonitor
// only keeping the player interface ID as it uniquely identifies the player in the event
final int mPlayerIId;
final int mEvent;
- final int mEventValue;
+ final int[] mEventValues;
- PlayerEvent(int piid, int event, int eventValue) {
+ PlayerEvent(int piid, int event, int[] eventValues) {
mPlayerIId = piid;
mEvent = event;
- mEventValue = eventValue;
+ mEventValues = eventValues;
}
@Override
@@ -1341,35 +1355,44 @@ public final class PlaybackActivityMonitor
switch (mEvent) {
case AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID:
return AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent) + " portId:"
- + mEventValue + " mapped to player piid:" + mPlayerIId;
+ + Arrays.toString(mEventValues) + " mapped to player piid:"
+ + mPlayerIId;
case AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID:
- if (mEventValue != 0) {
- builder.append(" deviceId:").append(mEventValue);
+ if ((mEventValues.length > 0) && (mEventValues[0] != 0)) {
+ builder.append(" deviceIds:").append(Arrays.toString(mEventValues));
}
return builder.toString();
case AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED:
builder.append(" source:");
- if (mEventValue <= 0) {
+ int eventValue = mEventValues[0];
+ if (eventValue <= 0) {
builder.append("none ");
} else {
- if ((mEventValue & MUTED_BY_MASTER) != 0) {
+ if ((eventValue & MUTED_BY_MASTER) != 0) {
builder.append("masterMute ");
}
- if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) {
+ if ((eventValue & MUTED_BY_STREAM_VOLUME) != 0) {
builder.append("streamVolume ");
}
- if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) {
+ if ((eventValue & MUTED_BY_STREAM_MUTED) != 0) {
builder.append("streamMute ");
}
- if ((mEventValue & MUTED_BY_APP_OPS) != 0) {
- builder.append("appOps ");
+ if ((eventValue & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+ builder.append("opPlayAudio ");
}
- if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
+ if ((eventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
builder.append("clientVolume ");
}
- if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
+ if ((eventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
builder.append("volumeShaper ");
}
+ if ((eventValue & MUTED_BY_PORT_VOLUME) != 0) {
+ builder.append("portVolume ");
+ }
+ if ((eventValue & MUTED_BY_OP_CONTROL_AUDIO) != 0) {
+ builder.append("opControlAudio ");
+ }
+
}
return builder.toString();
default:
@@ -1402,14 +1425,16 @@ public final class PlaybackActivityMonitor
private final int mPlayerIId;
private final int mPlayerType;
private final int mClientUid;
+ private final String mClientPackageName;
private final int mClientPid;
private final AudioAttributes mPlayerAttr;
private final int mSessionId;
- NewPlayerEvent(AudioPlaybackConfiguration apc) {
+ NewPlayerEvent(AudioPlaybackConfiguration apc, String packageName) {
mPlayerIId = apc.getPlayerInterfaceId();
mPlayerType = apc.getPlayerType();
mClientUid = apc.getClientUid();
+ mClientPackageName = packageName;
mClientPid = apc.getClientPid();
mPlayerAttr = apc.getAudioAttributes();
mSessionId = apc.getSessionId();
@@ -1418,7 +1443,7 @@ public final class PlaybackActivityMonitor
@Override
public String eventToString() {
return new String("new player piid:" + mPlayerIId + " uid/pid:" + mClientUid + "/"
- + mClientPid + " type:"
+ + mClientPid + " package:" + mClientPackageName + " type:"
+ AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ " attr:" + mPlayerAttr
+ " session:" + mSessionId);
@@ -1715,8 +1740,11 @@ public final class PlaybackActivityMonitor
synchronized (mPlayerLock) {
int piid = msg.arg1;
+
+ int[] eventValues = new int[1];
+ eventValues[0] = eventValue;
sEventLogger.enqueue(
- new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
+ new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValues));
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null || !apc.handleMutedEvent(eventValue)) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 9265ff2d9b2d..608edbb87861 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -59,6 +59,8 @@ import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -127,6 +129,8 @@ public class SpatializerHelper {
/** current level as reported by native Spatializer in callback */
private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ /** cached version of Spatializer.getSpatializedChannelMasks */
+ private List<Integer> mSpatializedChannelMasks = Collections.emptyList();
private boolean mTransauralSupported = false;
private boolean mBinauralSupported = false;
@@ -1028,6 +1032,17 @@ public class SpatializerHelper {
return;
}
try {
+ final int[] nativeMasks = mSpat.getSpatializedChannelMasks();
+ for (int i = 0; i < nativeMasks.length; i++) {
+ nativeMasks[i] = AudioFormat.convertNativeChannelMaskToOutMask(nativeMasks[i]);
+ }
+ mSpatializedChannelMasks = Arrays.stream(nativeMasks).boxed().toList();
+
+ } catch (Exception e) { // just catch Exception in case nativeMasks is null
+ Log.e(TAG, "Error calling getSpatializedChannelMasks", e);
+ mSpatializedChannelMasks = Collections.emptyList();
+ }
+ try {
//TODO: register heatracking callback only when sensors are registered
if (mIsHeadTrackingSupported) {
mActualHeadTrackingMode =
@@ -1100,6 +1115,10 @@ public class SpatializerHelper {
return able;
}
+ synchronized @NonNull List<Integer> getSpatializedChannelMasks() {
+ return mSpatializedChannelMasks;
+ }
+
//------------------------------------------------------
// head tracking
final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks =
@@ -1603,6 +1622,14 @@ public class SpatializerHelper {
pw.println("\tmState:" + mState);
pw.println("\tmSpatLevel:" + mSpatLevel);
pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel);
+ List<Integer> speakerMasks = getSpatializedChannelMasks();
+ StringBuilder masks = speakerMasks.isEmpty()
+ ? new StringBuilder("none") : new StringBuilder("");
+ for (Integer mask : speakerMasks) {
+ masks.append(AudioFormat.javaChannelOutMaskToString(mask)).append(" ");
+ }
+ pw.println("\tspatialized speaker masks: " + masks);
+
pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported);
StringBuilder modesString = new StringBuilder();
for (int mode : mSupportedHeadTrackingModes) {
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 1ea72d7da2fc..f66c7e115fc0 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -36,6 +36,7 @@ import android.os.UserManager;
import android.util.Slog;
import com.android.server.backup.Flags;
+import com.android.server.notification.NotificationBackupHelper;
import com.google.android.collect.Sets;
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index abfbddc18e24..290aab28a82d 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -305,7 +305,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mSensors /* sensorIds */,
true /* credentialAllowed */,
false /* requireConfirmation */,
- mUserId,
+ mPreAuthInfo.callingUserId,
mOperationId,
mOpPackageName,
mRequestId);
@@ -357,7 +357,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mSensors,
mPreAuthInfo.shouldShowCredential(),
requireConfirmation,
- mUserId,
+ mPreAuthInfo.callingUserId,
mOperationId,
mOpPackageName,
mRequestId);
@@ -491,7 +491,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mSensors /* sensorIds */,
true /* credentialAllowed */,
false /* requireConfirmation */,
- mUserId,
+ mPreAuthInfo.callingUserId,
mOperationId,
mOpPackageName,
mRequestId);
@@ -879,6 +879,14 @@ public final class AuthSession implements IBinder.DeathRecipient {
);
break;
+ case BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM:
+ mClientReceiver.onError(
+ getEligibleModalities(),
+ BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */
+ );
+ break;
+
default:
Slog.w(TAG, "Unhandled reason: " + reason);
break;
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index bc1e3c7171be..402448209860 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -288,11 +288,13 @@ public class BiometricService extends SystemService {
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public SettingObserver(Context context, Handler handler,
- List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
+ List<BiometricService.EnabledOnKeyguardCallback> callbacks,
+ UserManager userManager, FingerprintManager fingerprintManager,
+ FaceManager faceManager) {
super(handler);
mContentResolver = context.getContentResolver();
mCallbacks = callbacks;
- mUserManager = context.getSystemService(UserManager.class);
+ mUserManager = userManager;
final boolean hasFingerprint = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -304,7 +306,7 @@ public class BiometricService extends SystemService {
Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
&& hasFace && !hasFingerprint;
- addBiometricListenersForMandatoryBiometrics(context);
+ addBiometricListenersForMandatoryBiometrics(context, fingerprintManager, faceManager);
updateContentObserver();
}
@@ -436,6 +438,7 @@ public class BiometricService extends SystemService {
if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
updateMandatoryBiometricsRequirementsForAllProfiles(userId);
}
+
return mMandatoryBiometricsEnabled.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_STATUS)
&& mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
@@ -456,11 +459,23 @@ public class BiometricService extends SystemService {
private void updateMandatoryBiometricsForAllProfiles(int userId) {
int effectiveUserId = userId;
- if (mUserManager.getMainUser() != null) {
- effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+ if (parentProfile != null) {
+ effectiveUserId = parentProfile.id;
}
- for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
- mMandatoryBiometricsEnabled.put(profileUserId,
+
+ final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+ if (enabledProfileIds != null) {
+ for (int profileUserId : enabledProfileIds) {
+ mMandatoryBiometricsEnabled.put(profileUserId,
+ Settings.Secure.getIntForUser(
+ mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
+ effectiveUserId) != 0);
+ }
+ } else {
+ mMandatoryBiometricsEnabled.put(userId,
Settings.Secure.getIntForUser(
mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
@@ -470,11 +485,24 @@ public class BiometricService extends SystemService {
private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
int effectiveUserId = userId;
- if (mUserManager.getMainUser() != null) {
- effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+ if (parentProfile != null) {
+ effectiveUserId = parentProfile.id;
}
- for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
- mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+
+ final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+ if (enabledProfileIds != null) {
+ for (int profileUserId : enabledProfileIds) {
+ mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+ Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS
+ ? 1 : 0,
+ effectiveUserId) != 0);
+ }
+ } else {
+ mMandatoryBiometricsRequirementsSatisfied.put(userId,
Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
@@ -482,10 +510,8 @@ public class BiometricService extends SystemService {
}
}
- private void addBiometricListenersForMandatoryBiometrics(Context context) {
- final FingerprintManager fingerprintManager = context.getSystemService(
- FingerprintManager.class);
- final FaceManager faceManager = context.getSystemService(FaceManager.class);
+ private void addBiometricListenersForMandatoryBiometrics(Context context,
+ FingerprintManager fingerprintManager, FaceManager faceManager) {
if (fingerprintManager != null) {
fingerprintManager.addAuthenticatorsRegisteredCallback(
new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1129,7 +1155,7 @@ public class BiometricService extends SystemService {
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext(), mBiometricCameraManager);
+ getContext(), mBiometricCameraManager, mUserManager);
}
/**
@@ -1165,7 +1191,9 @@ public class BiometricService extends SystemService {
*/
public SettingObserver getSettingObserver(Context context, Handler handler,
List<EnabledOnKeyguardCallback> callbacks) {
- return new SettingObserver(context, handler, callbacks);
+ return new SettingObserver(context, handler, callbacks, context.getSystemService(
+ UserManager.class), context.getSystemService(FingerprintManager.class),
+ context.getSystemService(FaceManager.class));
}
/**
@@ -1516,9 +1544,9 @@ public class BiometricService extends SystemService {
mHandler.post(() -> {
try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
- mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
- opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext(), mBiometricCameraManager);
+ mDevicePolicyManager, mSettingObserver, mSensors, userId,
+ promptInfo, opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
+ getContext(), mBiometricCameraManager, mUserManager);
// Set the default title if necessary.
if (promptInfo.isUseDefaultTitle()) {
@@ -1568,8 +1596,8 @@ public class BiometricService extends SystemService {
promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
}
- authenticateInternal(token, requestId, operationId, userId, receiver,
- opPackageName, promptInfo, preAuthInfo);
+ authenticateInternal(token, requestId, operationId, preAuthInfo.userId,
+ receiver, opPackageName, promptInfo, preAuthInfo);
} else {
receiver.onError(preAuthStatus.first /* modality */,
preAuthStatus.second /* errorCode */,
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index bc38f65c9e4b..6ed1ac859501 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -32,6 +32,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.PromptInfo;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Pair;
import android.util.Slog;
@@ -72,6 +73,7 @@ class PreAuthInfo {
final boolean confirmationRequested;
final boolean ignoreEnrollmentState;
final int userId;
+ final int callingUserId;
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
@@ -82,7 +84,7 @@ class PreAuthInfo {
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
- PromptInfo promptInfo, int userId, Context context,
+ PromptInfo promptInfo, int userId, int callingUserId, Context context,
BiometricCameraManager biometricCameraManager,
boolean isOnlyMandatoryBiometricsRequested,
boolean isMandatoryBiometricsAuthentication) {
@@ -97,6 +99,7 @@ class PreAuthInfo {
this.confirmationRequested = promptInfo.isConfirmationRequested();
this.ignoreEnrollmentState = promptInfo.isIgnoreEnrollmentState();
this.userId = userId;
+ this.callingUserId = callingUserId;
this.context = context;
this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested;
this.mIsMandatoryBiometricsAuthentication = isMandatoryBiometricsAuthentication;
@@ -108,16 +111,24 @@ class PreAuthInfo {
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager, Context context,
- BiometricCameraManager biometricCameraManager)
+ BiometricCameraManager biometricCameraManager,
+ UserManager userManager)
throws RemoteException {
final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators()
- == BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
+ == BiometricManager.Authenticators.IDENTITY_CHECK;
boolean isMandatoryBiometricsAuthentication = false;
+ final int effectiveUserId;
+ if (Flags.effectiveUserBp()) {
+ effectiveUserId = userManager.getCredentialOwnerProfile(userId);
+ } else {
+ effectiveUserId = userId;
+ }
+
if (dropCredentialFallback(promptInfo.getAuthenticators(),
settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
- userId), trustManager)) {
+ effectiveUserId), trustManager)) {
isMandatoryBiometricsAuthentication = true;
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
if (promptInfo.getNegativeButtonText() == null) {
@@ -145,10 +156,9 @@ class PreAuthInfo {
for (BiometricSensor sensor : sensors) {
@AuthenticatorStatus int status = getStatusForBiometricAuthenticator(
- devicePolicyManager, settingObserver, sensor, userId, opPackageName,
- checkDevicePolicyManager, requestedStrength,
- promptInfo.getAllowedSensorIds(),
- promptInfo.isIgnoreEnrollmentState(),
+ devicePolicyManager, settingObserver, sensor, effectiveUserId,
+ opPackageName, checkDevicePolicyManager, requestedStrength,
+ promptInfo.getAllowedSensorIds(), promptInfo.isIgnoreEnrollmentState(),
biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName
@@ -172,16 +182,16 @@ class PreAuthInfo {
}
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
- eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId,
- context, biometricCameraManager, isOnlyMandatoryBiometricsRequested,
- isMandatoryBiometricsAuthentication);
+ eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo,
+ effectiveUserId, userId, context, biometricCameraManager,
+ isOnlyMandatoryBiometricsRequested, isMandatoryBiometricsAuthentication);
}
private static boolean dropCredentialFallback(int authenticators,
boolean isMandatoryBiometricsEnabled, ITrustManager trustManager) {
final boolean isMandatoryBiometricsRequested =
- (authenticators & BiometricManager.Authenticators.MANDATORY_BIOMETRICS)
- == BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
+ (authenticators & BiometricManager.Authenticators.IDENTITY_CHECK)
+ == BiometricManager.Authenticators.IDENTITY_CHECK;
if (Flags.mandatoryBiometrics() && isMandatoryBiometricsEnabled
&& isMandatoryBiometricsRequested) {
try {
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 87341365355b..c1f8e2e2923b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -147,7 +147,7 @@ public class Utils {
* @return true if mandatory biometrics is requested
*/
static boolean isMandatoryBiometricsRequested(@Authenticators.Types int authenticators) {
- return (authenticators & Authenticators.MANDATORY_BIOMETRICS) != 0;
+ return (authenticators & Authenticators.IDENTITY_CHECK) != 0;
}
/**
@@ -257,7 +257,7 @@ public class Utils {
if (Flags.mandatoryBiometrics()) {
testBits = ~(Authenticators.DEVICE_CREDENTIAL
| Authenticators.BIOMETRIC_MIN_STRENGTH
- | Authenticators.MANDATORY_BIOMETRICS);
+ | Authenticators.IDENTITY_CHECK);
} else {
testBits = ~(Authenticators.DEVICE_CREDENTIAL
| Authenticators.BIOMETRIC_MIN_STRENGTH);
@@ -329,8 +329,8 @@ public class Utils {
case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
break;
- case BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
- biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
+ case BiometricConstants.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE;
break;
case BiometricConstants.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS:
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
@@ -397,7 +397,7 @@ public class Utils {
case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
case MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR:
- return BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
+ return BiometricConstants.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE;
case BIOMETRIC_NOT_ENABLED_FOR_APPS:
if (Flags.mandatoryBiometrics()) {
return BiometricConstants.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index d3da8dd2cfda..95ba69580bba 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -34,3 +34,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "frr_dialog_improvement"
+ namespace: "biometrics_framework"
+ description: "This flag controls FRR dialog improvement"
+ bug: "380800403"
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index bc58501f5b59..30c73193e832 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -115,7 +115,7 @@ public class BiometricFrameworkStatsLogger {
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
public void enroll(int statsModality, int statsAction, int statsClient,
int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux,
- int source) {
+ int source, int templateId) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
statsModality,
targetUserId,
@@ -123,7 +123,28 @@ public class BiometricFrameworkStatsLogger {
enrollSuccessful,
-1, /* sensorId */
ambientLightLux,
- source);
+ source,
+ templateId);
+ }
+
+ /** {@see FrameworkStatsLog.BIOMETRIC_UNENROLLED} */
+ public void unenrolled(int statsModality, int targetUserId, int reason, int templateId) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_UNENROLLED,
+ statsModality,
+ targetUserId,
+ reason,
+ templateId);
+ }
+
+ /** {@see FrameworkStatsLog.BIOMETRIC_ENUMERATED} */
+ public void enumerated(int statsModality, int targetUserId, int result, int[] templateIdsHal,
+ int[] templateIdsFramework) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENUMERATED,
+ statsModality,
+ targetUserId,
+ result,
+ templateIdsHal,
+ templateIdsFramework);
}
/** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index 9351bc0811f2..08582ca7e507 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -32,6 +32,8 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
+import java.util.Arrays;
+
/**
* Logger for all reported Biometric framework events.
*/
@@ -254,7 +256,7 @@ public class BiometricLogger {
/** Log enrollment outcome. */
public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful,
- int source) {
+ int source, int templateId) {
if (!mShouldLogMetrics) {
return;
}
@@ -265,7 +267,8 @@ public class BiometricLogger {
+ ", Client: " + mStatsClient
+ ", Latency: " + latency
+ ", Lux: " + mALSProbe.getMostRecentLux()
- + ", Success: " + enrollSuccessful);
+ + ", Success: " + enrollSuccessful
+ + ", TemplateId: " + templateId);
} else {
Slog.v(TAG, "Enroll latency: " + latency);
}
@@ -275,7 +278,51 @@ public class BiometricLogger {
}
mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
- targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux(), source);
+ targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux(), source,
+ templateId);
+ }
+
+ /** Log un-enrollment. */
+ public void logOnUnEnrolled(int targetUserId, int reason, int templateId) {
+ if (!mShouldLogMetrics) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "UnEnrolled! Modality: " + mStatsModality
+ + ", User: " + targetUserId
+ + ", reason: " + reason
+ + ", templateId: " + templateId);
+ }
+
+ if (shouldSkipLogging()) {
+ return;
+ }
+
+ mSink.unenrolled(mStatsModality, targetUserId, reason, templateId);
+ }
+
+ /** Log enumeration. */
+ public void logOnEnumerated(int targetUserId, int result, int[] templateIdsHal,
+ int[] templateIdsFramework) {
+ if (!mShouldLogMetrics) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Enumerated! Modality: " + mStatsModality
+ + ", User: " + targetUserId
+ + ", result: " + result
+ + ", templateIdsHal: " + Arrays.toString(templateIdsHal)
+ + ", templateIdsFramework: " + Arrays.toString(templateIdsFramework));
+ }
+
+ if (shouldSkipLogging()) {
+ return;
+ }
+
+ mSink.enumerated(mStatsModality, targetUserId, result, templateIdsHal,
+ templateIdsFramework);
}
/** Report unexpected enrollment reported by the HAL. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index a1184156f86e..21c51403ecac 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -162,7 +162,7 @@ public class BiometricSchedulerOperation {
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING)) {
- return false;
+ return hasOperationAlreadyStarted();
}
if (mClientMonitor.getCookie() != 0) {
@@ -191,7 +191,7 @@ public class BiometricSchedulerOperation {
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING)) {
- return false;
+ return hasOperationAlreadyStarted();
}
return doStart(callback);
@@ -230,6 +230,10 @@ public class BiometricSchedulerOperation {
return true;
}
+ private boolean hasOperationAlreadyStarted() {
+ return mState == STATE_STARTED;
+ }
+
/**
* Abort a pending operation.
*
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 32c0bd389e2f..38bf99932838 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -95,7 +95,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier);
getLogger().logOnEnrolled(getTargetUserId(),
System.currentTimeMillis() - mEnrollmentStartTimeMs,
- true /* enrollSuccessful */, mEnrollReason);
+ true /* enrollSuccessful */, mEnrollReason, identifier.getBiometricId());
mCallback.onClientFinished(this, true /* success */);
}
notifyUserActivity();
@@ -123,7 +123,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
public void onError(int error, int vendorCode) {
getLogger().logOnEnrolled(getTargetUserId(),
System.currentTimeMillis() - mEnrollmentStartTimeMs,
- false /* enrollSuccessful */, mEnrollReason);
+ false /* enrollSuccessful */, mEnrollReason, -1 /* templateId */);
super.onError(error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 6c305599c39f..03b382e0a9d2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.Build;
import android.os.IBinder;
import android.util.Slog;
@@ -135,7 +136,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
BiometricUtils<S> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Map<Integer, Long> authenticatorIds);
+ Map<Integer, Long> authenticatorIds, int reason);
protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
int userId, @NonNull String owner, int sensorId,
@@ -158,7 +159,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
template.mIdentifier.getBiometricId(), template.mUserId,
getContext().getPackageName(), mBiometricUtils, getSensorId(),
- getLogger(), getBiometricContext(), mAuthenticatorIds);
+ getLogger(), getBiometricContext(), mAuthenticatorIds,
+ BiometricsProtoEnums.UNENROLL_REASON_DANGLING_HAL);
getLogger().logUnknownEnrollmentInHal();
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 2c2c685fd442..5a47f1beab51 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.util.Slog;
@@ -46,6 +47,10 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
// List of templates to remove from the HAL
private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
private final int mInitialEnrolledSize;
+ private final int[] mEnrolledIdsFrameworkArray;
+ private final List<Integer> mEnrolledIdsHalList = new ArrayList<>();
+ private boolean mIsDanglingFramework;
+ private boolean mIsDanglingHal;
protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, int userId, @NonNull String owner,
@@ -60,14 +65,26 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
mEnrolledList = enrolledList;
mInitialEnrolledSize = mEnrolledList.size();
mUtils = utils;
+ // Record ids from frameworks for metrics
+ mEnrolledIdsFrameworkArray = new int[mInitialEnrolledSize];
+ for (int i = 0; i < mInitialEnrolledSize; i++) {
+ mEnrolledIdsFrameworkArray[i] = mEnrolledList.get(i).getBiometricId();
+ }
}
@Override
public void onEnumerationResult(BiometricAuthenticator.Identifier identifier,
int remaining) {
+ if (identifier != null) {
+ // Record ids from hal for metrics
+ mEnrolledIdsHalList.add(identifier.getBiometricId());
+ }
handleEnumeratedTemplate(identifier);
if (remaining == 0) {
+ mIsDanglingHal = !mUnknownHALTemplates.isEmpty();
+ mIsDanglingFramework = !mEnrolledList.isEmpty();
doTemplateCleanup();
+ logEnumerationResult();
mCallback.onClientFinished(this, true /* success */);
}
}
@@ -123,7 +140,9 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
+ identifier.getBiometricId() + " " + identifier.getName());
mUtils.removeBiometricForUser(getContext(),
getTargetUserId(), identifier.getBiometricId());
-
+ getLogger().logOnUnEnrolled(getTargetUserId(),
+ BiometricsProtoEnums.UNENROLL_REASON_DANGLING_FRAMEWORK,
+ identifier.getBiometricId());
getLogger().logUnknownEnrollmentInFramework();
}
@@ -134,6 +153,32 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
mEnrolledList.clear();
}
+ private void logEnumerationResult() {
+ final int result;
+ if (mIsDanglingFramework && mIsDanglingHal) {
+ result = BiometricsProtoEnums.ENUMERATION_RESULT_DANGLING_BOTH;
+ } else if (mIsDanglingFramework) {
+ result = BiometricsProtoEnums.ENUMERATION_RESULT_DANGLING_FRAMEWORK;
+ } else if (mIsDanglingHal) {
+ result = BiometricsProtoEnums.ENUMERATION_RESULT_DANGLING_HAL;
+ } else {
+ result = BiometricsProtoEnums.ENUMERATION_RESULT_OK;
+ }
+
+ final int[] idsHalArray = listToArray(mEnrolledIdsHalList);
+ getLogger().logOnEnumerated(
+ getTargetUserId(), result, idsHalArray, mEnrolledIdsFrameworkArray);
+ }
+
+ private int[] listToArray(List<Integer> ids) {
+ final int size = ids.size();
+ int[] array = new int[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = ids.get(i);
+ }
+ return array;
+ }
+
public List<BiometricAuthenticator.Identifier> getUnknownHALTemplates() {
return mUnknownHALTemplates;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index ad5877aed6da..0259906806f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -42,17 +42,19 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final boolean mHasEnrollmentsBeforeStarting;
+ private final int mReason;
public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
+ @NonNull Map<Integer, Long> authenticatorIds, int reason) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
logger, biometricContext, false /* isMandatoryBiometrics */);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
+ mReason = reason;
}
@Override
@@ -87,6 +89,7 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining);
mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(),
identifier.getBiometricId());
+ getLogger().logOnUnEnrolled(getTargetUserId(), mReason, identifier.getBiometricId());
try {
getListener().onRemoved(identifier, remaining);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index c27b7c483afc..5adc25194f8d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -63,12 +63,12 @@ public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlS
Supplier<AidlSession> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Map<Integer, Long> authenticatorIds) {
+ Map<Integer, Long> authenticatorIds, int reason) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FaceRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
- utils, sensorId, logger, biometricContext, authenticatorIds);
+ utils, sensorId, logger, biometricContext, authenticatorIds, reason);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 85d3cceca6f8..944e85cc970e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -668,7 +668,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext,
- mFaceSensors.get(sensorId).getAuthenticatorIds());
+ mFaceSensors.get(sensorId).getAuthenticatorIds(),
+ BiometricsProtoEnums.UNENROLL_REASON_USER_REQUEST);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 079388822def..971cfbb72bdb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -46,9 +46,9 @@ public class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
int[] biometricIds, int userId, @NonNull String owner,
@NonNull BiometricUtils<Face> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
+ @NonNull Map<Integer, Long> authenticatorIds, int reason) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- logger, biometricContext, authenticatorIds);
+ logger, biometricContext, authenticatorIds, reason);
mBiometricIds = biometricIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 40b8a45beb36..77670ecca1fc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -71,11 +71,11 @@ public class FingerprintInternalCleanupClient
Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId,
String owner, BiometricUtils<Fingerprint> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Map<Integer, Long> authenticatorIds) {
+ Map<Integer, Long> authenticatorIds, int reason) {
return new FingerprintRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE),
- biometricContext, authenticatorIds);
+ biometricContext, authenticatorIds, reason);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 456591c3076f..c18d92530135 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -659,7 +659,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
sensorId, createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector), mBiometricContext,
- mFingerprintSensors.get(sensorId).getAuthenticatorIds());
+ mFingerprintSensors.get(sensorId).getAuthenticatorIds(),
+ BiometricsProtoEnums.UNENROLL_REASON_USER_REQUEST);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index 4f08f6fbde54..c6b955acbb8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -47,9 +47,9 @@ public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSes
@Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
+ @NonNull Map<Integer, Long> authenticatorIds, int reason) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- logger, biometricContext, authenticatorIds);
+ logger, biometricContext, authenticatorIds, reason);
mBiometricIds = biometricIds;
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index a3c68f9dc827..afffa6603b3d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.hardware.broadcastradio.Alert;
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.Announcement;
import android.hardware.broadcastradio.ConfigFlag;
@@ -36,6 +37,7 @@ import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.Flags;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioAlert;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
@@ -573,6 +575,86 @@ final class ConversionUtils {
return builder.build();
}
+ @Nullable private static RadioAlert.Polygon polygonFromHalPolygon(
+ android.hardware.broadcastradio.Polygon halPolygon) {
+ if (halPolygon.coordinates.length < 4) {
+ Slogf.e(TAG, "Number of coordinates in alert polygon cannot be less than 4");
+ return null;
+ } else if (halPolygon.coordinates[0].latitude
+ != halPolygon.coordinates[halPolygon.coordinates.length - 1].latitude
+ || halPolygon.coordinates[0].longitude
+ != halPolygon.coordinates[halPolygon.coordinates.length - 1].longitude) {
+ Slogf.e(TAG, "The first and the last coordinate in alert polygon cannot be different");
+ return null;
+ }
+ List<RadioAlert.Coordinate> coordinates = new ArrayList<>(halPolygon.coordinates.length);
+ for (int idx = 0; idx < halPolygon.coordinates.length; idx++) {
+ coordinates.add(new RadioAlert.Coordinate(halPolygon.coordinates[idx].latitude,
+ halPolygon.coordinates[idx].longitude));
+ }
+ return new RadioAlert.Polygon(coordinates);
+ }
+
+ private static RadioAlert.Geocode geocodeFromHalGeocode(
+ android.hardware.broadcastradio.Geocode geocode) {
+ return new RadioAlert.Geocode(geocode.valueName, geocode.value);
+ }
+
+ private static RadioAlert.AlertArea alertAreaFromHalAlertArea(
+ android.hardware.broadcastradio.AlertArea halAlertArea) {
+ List<RadioAlert.Polygon> polygonList = new ArrayList<>();
+ for (int idx = 0; idx < halAlertArea.polygons.length; idx++) {
+ RadioAlert.Polygon polygon = polygonFromHalPolygon(halAlertArea.polygons[idx]);
+ if (polygon != null) {
+ polygonList.add(polygon);
+ }
+ }
+ List<RadioAlert.Geocode> geocodeList = new ArrayList<>(halAlertArea.geocodes.length);
+ for (int idx = 0; idx < halAlertArea.geocodes.length; idx++) {
+ geocodeList.add(geocodeFromHalGeocode(halAlertArea.geocodes[idx]));
+ }
+ return new RadioAlert.AlertArea(polygonList, geocodeList);
+ }
+
+ private static RadioAlert.AlertInfo alertInfoFromHalAlertInfo(
+ android.hardware.broadcastradio.AlertInfo halAlertInfo) {
+ int[] categoryArray = new int[halAlertInfo.categoryArray.length];
+ for (int idx = 0; idx < halAlertInfo.categoryArray.length; idx++) {
+ // Integer values in android.hardware.radio.RadioAlert.AlertCategory and
+ // android.hardware.broadcastradio.AlertCategory match.
+ categoryArray[idx] = halAlertInfo.categoryArray[idx];
+ }
+ List<RadioAlert.AlertArea> alertAreaList = new ArrayList<>();
+ for (int idx = 0; idx < halAlertInfo.areas.length; idx++) {
+ alertAreaList.add(alertAreaFromHalAlertArea(halAlertInfo.areas[idx]));
+ }
+ // Integer values in android.hardware.radio.RadioAlert.AlertUrgency and
+ // android.hardware.broadcastradio.AlertUrgency match.
+ // Integer values in android.hardware.radio.RadioAlert.AlertSeverity and
+ // android.hardware.broadcastradio.AlertSeverity match.
+ // Integer values in android.hardware.radio.RadioAlert.AlertCertainty and
+ // android.hardware.broadcastradio.AlertCertainty match.
+ return new RadioAlert.AlertInfo(categoryArray, halAlertInfo.urgency, halAlertInfo.severity,
+ halAlertInfo.certainty, halAlertInfo.description, alertAreaList,
+ halAlertInfo.language);
+ }
+
+ @VisibleForTesting
+ @Nullable static RadioAlert radioAlertFromHalAlert(Alert halAlert) {
+ if (halAlert == null) {
+ return null;
+ }
+ List<RadioAlert.AlertInfo> alertInfo = new ArrayList<>(halAlert.infoArray.length);
+ for (int idx = 0; idx < halAlert.infoArray.length; idx++) {
+ alertInfo.add(alertInfoFromHalAlertInfo(halAlert.infoArray[idx]));
+ }
+ // Integer values in android.hardware.radio.RadioAlert.AlertStatus and
+ // android.hardware.broadcastradio.AlertStatus match.
+ // Integer values in android.hardware.radio.RadioAlert.AlertMessageType and
+ // android.hardware.broadcastradio.AlertMessageType match.
+ return new RadioAlert(halAlert.status, halAlert.messageType, alertInfo);
+ }
+
private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) {
return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI
|| id.type == IdentifierType.HD_STATION_ID_EXT
@@ -605,7 +687,18 @@ final class ConversionUtils {
}
}
}
-
+ if (!Flags.hdRadioEmergencyAlertSystem()) {
+ return new RadioManager.ProgramInfo(
+ Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
+ identifierFromHalProgramIdentifier(info.logicallyTunedTo),
+ identifierFromHalProgramIdentifier(info.physicallyTunedTo),
+ relatedContent,
+ info.infoFlags,
+ info.signalQuality,
+ radioMetadataFromHalMetadata(info.metadata),
+ vendorInfoFromHalVendorKeyValues(info.vendorInfo)
+ );
+ }
return new RadioManager.ProgramInfo(
Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
identifierFromHalProgramIdentifier(info.logicallyTunedTo),
@@ -614,7 +707,8 @@ final class ConversionUtils {
info.infoFlags,
info.signalQuality,
radioMetadataFromHalMetadata(info.metadata),
- vendorInfoFromHalVendorKeyValues(info.vendorInfo)
+ vendorInfoFromHalVendorKeyValues(info.vendorInfo),
+ radioAlertFromHalAlert(info.emergencyAlert)
);
}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 6e38733f04c2..471b7b4ddfc8 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -167,6 +167,12 @@ public abstract class VirtualDeviceManagerInternal {
*/
public abstract int getDeviceIdForDisplayId(int displayId);
+ /** Returns the dim duration for the displays of the device with the given ID. */
+ public abstract long getDimDurationMillisForDeviceId(int deviceId);
+
+ /** Returns the screen off timeout of the displays of the device with the given ID. */
+ public abstract long getScreenOffTimeoutMillisForDeviceId(int deviceId);
+
/**
* Gets the persistent ID for the VirtualDevice with the given device ID.
*
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index c34fc696aeed..6feae34f1a2d 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.Binder;
@@ -75,6 +76,7 @@ public class PlatformCompat extends IPlatformCompat.Stub {
private final ChangeReporter mChangeReporter;
private final CompatConfig mCompatConfig;
private final AndroidBuildClassifier mBuildClassifier;
+ private Boolean mIsWear;
public PlatformCompat(Context context) {
super(PermissionEnforcer.fromContext(context));
@@ -246,7 +248,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
boolean enabled = true;
final int userId = UserHandle.getUserId(uid);
for (String packageName : packages) {
- final var appInfo = getApplicationInfo(packageName, userId);
+ final var appInfo =
+ fixTargetSdk(getApplicationInfo(packageName, userId), uid);
enabled &= isChangeEnabledInternal(changeId, appInfo);
}
return enabled;
@@ -265,7 +268,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
boolean enabled = true;
final int userId = UserHandle.getUserId(uid);
for (String packageName : packages) {
- final var appInfo = getApplicationInfo(packageName, userId);
+ final var appInfo =
+ fixTargetSdk(getApplicationInfo(packageName, userId), uid);
enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo);
}
return enabled;
@@ -508,6 +512,22 @@ public class PlatformCompat extends IPlatformCompat.Stub {
packageName, 0, Process.myUid(), userId);
}
+ private ApplicationInfo fixTargetSdk(ApplicationInfo appInfo, int uid) {
+
+ // mIsWear doesn't need to be locked, ok if executes twice
+ if (mIsWear == null) {
+ mIsWear = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
+ // b/282922910 - we don't want apps sharing system uid and targeting
+ // older target sdk to impact all system uid apps
+ if (Flags.systemUidTargetSystemSdk() && !mIsWear &&
+ uid == Process.SYSTEM_UID && appInfo != null) {
+ appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
+ }
+ return appInfo;
+ }
+
@android.ravenwood.annotation.RavenwoodReplace
private void killPackage(String packageName) {
int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName,
diff --git a/services/core/java/com/android/server/compat/platform_compat_flags.aconfig b/services/core/java/com/android/server/compat/platform_compat_flags.aconfig
new file mode 100644
index 000000000000..fb323238c38e
--- /dev/null
+++ b/services/core/java/com/android/server/compat/platform_compat_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.compat"
+container: "system"
+
+flag {
+ name: "system_uid_target_system_sdk"
+ namespace: "app_compat"
+ description: "Compat framework feature flag for forcing all system uid apps to target system sdk"
+ bug: "29702703"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index fc20ef20f63d..7154bc47fc33 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -73,6 +73,8 @@ public class SyncLogger {
*/
public static synchronized SyncLogger getInstance() {
if (sInstance == null) {
+ // Always default to the sync logger for now (see b/381957278#comment8).
+ /*
final String flag = SystemProperties.get("debug.synclog");
final boolean enable =
(Build.IS_DEBUGGABLE
@@ -83,6 +85,8 @@ public class SyncLogger {
} else {
sInstance = new SyncLogger();
}
+ */
+ sInstance = new SyncLogger();
}
return sInstance;
}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
index 8e725465ddd6..eef2b15c3387 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -93,7 +93,8 @@ public final class CrashRecoveryHelper {
return;
}
final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
- PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ PackageWatchdog.getInstance(mContext).notifyPackageFailure(pkgList,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
});
}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
deleted file mode 100644
index 5f2fbcedce88..000000000000
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.crashrecovery;
-
-import android.content.Context;
-
-import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
-import com.android.server.SystemService;
-
-
-/** This class encapsulate the lifecycle methods of CrashRecovery module. */
-public class CrashRecoveryModule {
- private static final String TAG = "CrashRecoveryModule";
-
- /** Lifecycle definition for CrashRecovery module. */
- public static class Lifecycle extends SystemService {
- private Context mSystemContext;
- private PackageWatchdog mPackageWatchdog;
-
- public Lifecycle(Context context) {
- super(context);
- mSystemContext = context;
- mPackageWatchdog = PackageWatchdog.getInstance(context);
- }
-
- @Override
- public void onStart() {
- RescueParty.registerHealthObserver(mSystemContext);
- mPackageWatchdog.registerShutdownBroadcastReceiver();
- mPackageWatchdog.noteBoot();
- }
-
- @Override
- public void onBootPhase(int phase) {
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mPackageWatchdog.onPackagesReady();
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
deleted file mode 100644
index 3eb3380a57a0..000000000000
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.crashrecovery;
-
-import android.os.Environment;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-
-/**
- * Class containing helper methods for the CrashRecoveryModule.
- *
- * @hide
- */
-public class CrashRecoveryUtils {
- private static final String TAG = "CrashRecoveryUtils";
- private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB
- private static final Object sFileLock = new Object();
-
- /** Persist recovery related events in crashrecovery events file.**/
- public static void logCrashRecoveryEvent(int priority, String msg) {
- Slog.println(priority, TAG, msg);
- try {
- File fname = getCrashRecoveryEventsFile();
- synchronized (sFileLock) {
- FileOutputStream out = new FileOutputStream(fname, true);
- PrintWriter pw = new PrintWriter(out);
- String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString();
- pw.println(dateString + ": " + msg);
- pw.close();
- }
- } catch (IOException e) {
- Slog.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
- }
- }
-
- /** Dump recovery related events from crashrecovery events file.**/
- public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) {
- pw.println("CrashRecovery Events: ");
- pw.increaseIndent();
- final File file = getCrashRecoveryEventsFile();
- final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE;
- synchronized (sFileLock) {
- try (BufferedReader in = new BufferedReader(new FileReader(file))) {
- if (skipSize > 0) {
- in.skip(skipSize);
- }
- String line;
- while ((line = in.readLine()) != null) {
- pw.println(line);
- }
- } catch (IOException e) {
- Slog.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
- }
- }
- pw.decreaseIndent();
- }
-
- private static File getCrashRecoveryEventsFile() {
- File systemDir = new File(Environment.getDataDirectory(), "system");
- return new File(systemDir, "crashrecovery-events.txt");
- }
-}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 1094bee1e7e6..5d9db65fe2b2 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,8 +19,19 @@ package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_DUAL_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_REAR_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
@@ -44,6 +55,10 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IProcessObserver;
import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
@@ -56,9 +71,12 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.LongSparseLongArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -130,6 +148,8 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull
private final BinderService mBinderService;
@NonNull
+ private final HalService mHalService;
+ @NonNull
private final OverrideRequestController mOverrideRequestController;
@NonNull
private final DeviceStateProviderListener mDeviceStateProviderListener;
@@ -139,7 +159,7 @@ public final class DeviceStateManagerService extends SystemService {
// All supported device states keyed by identifier.
@GuardedBy("mLock")
- private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
+ private final SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
// The current committed device state. Will be empty until the first device state provided by
// the DeviceStateProvider is committed.
@@ -177,7 +197,7 @@ public final class DeviceStateManagerService extends SystemService {
@GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
- private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
+ private final Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
private Set<Integer> mFoldedDeviceStates = new HashSet<>();
@@ -259,6 +279,7 @@ public final class DeviceStateManagerService extends SystemService {
mDeviceStateProviderListener = new DeviceStateProviderListener();
mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
mBinderService = new BinderService();
+ mHalService = new HalService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mDeviceStateNotificationController = new DeviceStateNotificationController(
context, mHandler,
@@ -272,6 +293,10 @@ public final class DeviceStateManagerService extends SystemService {
@Override
public void onStart() {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
+ String halServiceName = IDeviceStateService.DESCRIPTOR + "/default";
+ if (ServiceManager.isDeclared(halServiceName)) {
+ publishBinderService(halServiceName, mHalService);
+ }
publishLocalService(DeviceStateManagerInternal.class, new LocalService());
if (!Flags.deviceStatePropertyMigration()) {
@@ -440,6 +465,11 @@ public final class DeviceStateManagerService extends SystemService {
return mBinderService;
}
+ @VisibleForTesting
+ IDeviceStateService getHalBinderService() {
+ return mHalService;
+ }
+
private void updateSupportedStates(DeviceState[] supportedDeviceStates,
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
synchronized (mLock) {
@@ -778,15 +808,16 @@ public final class DeviceStateManagerService extends SystemService {
processRecord.notifyRequestActiveAsync(request.getToken());
}
- private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
+ @Nullable
+ private DeviceStateInfo registerProcess(int pid, IDeviceStateManagerCallback callback) {
synchronized (mLock) {
if (mProcessRecords.contains(pid)) {
throw new SecurityException("The calling process has already registered an"
+ " IDeviceStateManagerCallback.");
}
- ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied,
- mHandler);
+ final ProcessRecord record =
+ new ProcessRecord(callback, pid, this::handleProcessDied, mHandler);
try {
callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) {
@@ -794,15 +825,20 @@ public final class DeviceStateManagerService extends SystemService {
}
mProcessRecords.put(pid, record);
- // Callback clients should not be notified of invalid device states, so calls to
- // #getDeviceStateInfoLocked should be gated on checks if a committed state is present
- // before getting the device state info.
- DeviceStateInfo currentInfo = mCommittedState.isPresent()
- ? getDeviceStateInfoLocked() : null;
- if (currentInfo != null) {
- // If there is not a committed state we'll wait to notify the process of the initial
- // value.
- record.notifyDeviceStateInfoAsync(currentInfo);
+ final DeviceStateInfo currentInfo =
+ mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null;
+ if (com.android.window.flags.Flags.wlinfoOncreate()) {
+ return currentInfo;
+ } else {
+ // Callback clients should not be notified of invalid device states, so calls to
+ // #getDeviceStateInfoLocked should be gated on checks if a committed state is
+ // present before getting the device state info.
+ if (currentInfo != null) {
+ // If there is not a committed state we'll wait to notify the process of the
+ // initial value.
+ record.notifyDeviceStateInfoAsync(currentInfo);
+ }
+ return null;
}
}
}
@@ -819,7 +855,7 @@ public final class DeviceStateManagerService extends SystemService {
}
}
- private void requestStateInternal(int state, int flags, int callingPid, int callingUid,
+ private void requestStateInternal(int requestedState, int flags, int callingPid, int callingUid,
@NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -834,19 +870,30 @@ public final class DeviceStateManagerService extends SystemService {
+ " token: " + token);
}
- final Optional<DeviceState> deviceState = getStateLocked(state);
- if (!deviceState.isPresent()) {
- throw new IllegalArgumentException("Requested state: " + state
+ final Optional<DeviceState> requestedDeviceState = getStateLocked(requestedState);
+ if (requestedDeviceState.isEmpty()) {
+ throw new IllegalArgumentException("Requested state: " + requestedState
+ " is not supported.");
}
- OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
- deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+ final OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+ requestedDeviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
if (Flags.deviceStatePropertyMigration()) {
- // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
- if (!hasControlDeviceStatePermission && deviceState.get().hasProperty(
- PROPERTY_FEATURE_REAR_DISPLAY)) {
+ final boolean isRequestingRdm = requestedDeviceState.get()
+ .hasProperty(PROPERTY_FEATURE_REAR_DISPLAY);
+ final boolean isRequestingRdmOuterDefault = requestedDeviceState.get()
+ .hasProperty(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT);
+
+ final boolean isDeviceClosed = mCommittedState.isEmpty() ? false
+ : mCommittedState.get().hasProperty(
+ PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+
+ final boolean shouldShowRdmEduDialog = isRequestingRdm && shouldShowRdmEduDialog(
+ hasControlDeviceStatePermission, isRequestingRdmOuterDefault,
+ isDeviceClosed);
+
+ if (shouldShowRdmEduDialog) {
showRearDisplayEducationalOverlayLocked(request);
} else {
mOverrideRequestController.addRequest(request);
@@ -854,7 +901,7 @@ public final class DeviceStateManagerService extends SystemService {
} else {
// If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
if (!hasControlDeviceStatePermission && mRearDisplayState != null
- && state == mRearDisplayState.getIdentifier()) {
+ && requestedState == mRearDisplayState.getIdentifier()) {
showRearDisplayEducationalOverlayLocked(request);
} else {
mOverrideRequestController.addRequest(request);
@@ -864,6 +911,28 @@ public final class DeviceStateManagerService extends SystemService {
}
/**
+ * Determines if the system should show an educational dialog before entering rear display mode
+ * @param hasControlDeviceStatePermission If the app has the CONTROL_DEVICE_STATE permission, we
+ * don't need to show the overlay
+ * @param requestingRdmOuterDefault True if the system is requesting
+ * PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT
+ * @param isDeviceClosed True if the device is closed (folded) when the request was made
+ */
+ @VisibleForTesting
+ static boolean shouldShowRdmEduDialog(boolean hasControlDeviceStatePermission,
+ boolean requestingRdmOuterDefault, boolean isDeviceClosed) {
+ if (hasControlDeviceStatePermission) {
+ return false;
+ }
+
+ if (requestingRdmOuterDefault) {
+ return isDeviceClosed;
+ } else {
+ return true;
+ }
+ }
+
+ /**
* If we get a request to enter rear display mode, we need to display an educational
* overlay to let the user know what will happen. This calls into the
* {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog.
@@ -1276,6 +1345,124 @@ public final class DeviceStateManagerService extends SystemService {
}
}
+ private final class HalService extends IDeviceStateService.Stub {
+ private final LongSparseLongArray mPublicProperties = new LongSparseLongArray();
+ public HalService() {
+ mPublicProperties.put(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
+ FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+ mPublicProperties.put(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN,
+ FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN);
+ mPublicProperties.put(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN,
+ FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN);
+ mPublicProperties.put(
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ mPublicProperties.put(
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+ FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
+ mPublicProperties.put(
+ PROPERTY_FEATURE_REAR_DISPLAY,
+ FEATURE_REAR_DISPLAY);
+ mPublicProperties.put(
+ PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+ FEATURE_DUAL_DISPLAY);
+ }
+
+ private final class HalBinderCallback implements IDeviceStateManagerCallback {
+ private final IDeviceStateListener mListener;
+
+ private HalBinderCallback(@NonNull IDeviceStateListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onDeviceStateInfoChanged(DeviceStateInfo info) throws RemoteException {
+ DeviceStateConfiguration config = new DeviceStateConfiguration();
+ Set<Integer> systemProperties = new HashSet<>(
+ info.currentState.getConfiguration().getSystemProperties());
+ Set<Integer> physicalProperties = new HashSet<>(
+ info.currentState.getConfiguration().getPhysicalProperties());
+ config.deviceProperties = 0;
+ for (Integer prop : systemProperties) {
+ Long publicProperty = mPublicProperties.get(prop);
+ if (publicProperty != null) {
+ config.deviceProperties |= publicProperty.longValue();
+ }
+ }
+ for (Integer prop : physicalProperties) {
+ Long publicProperty = mPublicProperties.get(prop);
+ if (publicProperty != null) {
+ config.deviceProperties |= publicProperty.longValue();
+ }
+ }
+ mListener.onDeviceStateChanged(config);
+ }
+
+ @Override
+ public void onRequestActive(IBinder token) {
+ //No-op
+ }
+
+ @Override
+ public void onRequestCanceled(IBinder token) {
+ //No-op
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mListener.asBinder();
+ }
+ }
+
+ @Override
+ public void registerListener(IDeviceStateListener listener) throws RemoteException {
+ if (listener == null) {
+ throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+ }
+
+ final int callingPid = Binder.getCallingPid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ HalBinderCallback callback = new HalBinderCallback(listener);
+ DeviceStateInfo info = registerProcess(callingPid, callback);
+ if (info != null) {
+ callback.onDeviceStateInfoChanged(info);
+ }
+ } catch (SecurityException e) {
+ throw new ServiceSpecificException(ErrorCode.ALREADY_EXISTS);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterListener(IDeviceStateListener listener) throws RemoteException {
+ final int callingPid = Binder.getCallingPid();
+
+ synchronized (mLock) {
+ if (mProcessRecords.contains(callingPid)) {
+ mProcessRecords.remove(callingPid);
+ return;
+ }
+ }
+
+ throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return IDeviceStateService.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return IDeviceStateService.HASH;
+ }
+ }
+
/** Implementation of {@link IDeviceStateManager} published as a binder service. */
private final class BinderService extends IDeviceStateManager.Stub {
@@ -1286,8 +1473,9 @@ public final class DeviceStateManagerService extends SystemService {
}
}
+ @Nullable
@Override // Binder call
- public void registerCallback(IDeviceStateManagerCallback callback) {
+ public DeviceStateInfo registerCallback(IDeviceStateManagerCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("Device state callback must not be null.");
}
@@ -1295,7 +1483,7 @@ public final class DeviceStateManagerService extends SystemService {
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
- registerProcess(callingPid, callback);
+ return registerProcess(callingPid, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 86015acc232f..805357e79565 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,7 +82,8 @@ public class AutomaticBrightnessController {
@IntDef(prefix = { "AUTO_BRIGHTNESS_MODE_" }, value = {
AUTO_BRIGHTNESS_MODE_DEFAULT,
AUTO_BRIGHTNESS_MODE_IDLE,
- AUTO_BRIGHTNESS_MODE_DOZE
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutomaticBrightnessMode{}
@@ -90,6 +91,7 @@ public class AutomaticBrightnessController {
public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+ public static final int AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR = 3;
public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
// How long the current sensor reading is assumed to be valid beyond the current time.
@@ -1278,9 +1280,10 @@ public class AutomaticBrightnessController {
private boolean shouldApplyDozeScaleFactor() {
// We don't apply the doze scale factor if we have a designated brightness curve for doze.
- return (mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()
- ? !mUseNormalBrightnessForDoze && mDisplayPolicy == POLICY_DOZE
- : Display.isDozeState(mDisplayState)) && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
+ return (mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)
+ ? (!mUseNormalBrightnessForDoze && mDisplayPolicy == POLICY_DOZE)
+ || Display.isDozeState(mDisplayState) : Display.isDozeState(mDisplayState))
+ && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
}
private class ShortTermModel {
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 6a019f3d024c..570d5d0fc134 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static android.text.TextUtils.formatSimple;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
@@ -114,7 +115,7 @@ public abstract class BrightnessMappingStrategy {
luxLevels = getLuxLevels(context.getResources().getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
}
- case AUTO_BRIGHTNESS_MODE_DOZE -> {
+ case AUTO_BRIGHTNESS_MODE_DOZE, AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR -> {
luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
brightnessLevels =
displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 1d68ee54d96c..83b0801ce87f 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -67,6 +67,10 @@ class BrightnessRangeController {
mNormalBrightnessModeController.resetNbmData(
displayDeviceConfig.getLuxThrottlingData());
}
+ if (flags.useNewHdrBrightnessModifier()) {
+ // HDR boost is handled by HdrBrightnessModifier and should be disabled in HbmController
+ mHbmController.disableHdrBoost();
+ }
updateHdrClamper(info, displayToken, displayDeviceConfig);
}
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 0f65360e9ee4..c19d2c9091c3 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -130,6 +130,8 @@ public class BrightnessTracker {
private static final int MSG_START_SENSOR_LISTENER = 3;
private static final int MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED = 4;
private static final int MSG_SENSOR_CHANGED = 5;
+ private static final int MSG_START_DISPLAY_LISTENER = 6;
+ private static final int MSG_STOP_DISPLAY_LISTENER = 7;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -159,7 +161,9 @@ public class BrightnessTracker {
private SensorListener mSensorListener;
private Sensor mLightSensor;
private SettingsObserver mSettingsObserver;
- private DisplayListener mDisplayListener;
+ private final DisplayListener mDisplayListener = new DisplayListener();
+ private boolean mDisplayListenerRegistered;
+ private boolean mIsDisplayActive;
private boolean mSensorRegistered;
private boolean mColorSamplingEnabled;
private int mNoFramesToSample;
@@ -231,6 +235,8 @@ public class BrightnessTracker {
mSettingsObserver = new SettingsObserver(mBgHandler);
mInjector.registerBrightnessModeObserver(mContentResolver, mSettingsObserver);
+ startDisplayListener();
+ mIsDisplayActive = isDisplayActive();
startSensorListener();
final IntentFilter intentFilter = new IntentFilter();
@@ -260,6 +266,7 @@ public class BrightnessTracker {
Slog.d(TAG, "Stop");
}
mBgHandler.removeMessages(MSG_BACKGROUND_START);
+ stopDisplayListener();
stopSensorListener();
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterBrightnessModeObserver(mContext, mSettingsObserver);
@@ -443,6 +450,11 @@ public class BrightnessTracker {
private void handleSensorChanged(Sensor lightSensor) {
if (mLightSensor != lightSensor) {
mLightSensor = lightSensor;
+ if (lightSensor != null) {
+ mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
+ } else {
+ mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
+ }
stopSensorListener();
// Attempt to restart the sensor listener. It will check to see if it should be running
// so there is no need to also check here.
@@ -455,6 +467,7 @@ public class BrightnessTracker {
&& mLightSensor != null
&& mAmbientBrightnessStatsTracker != null
&& mInjector.isInteractive(mContext)
+ && mIsDisplayActive
&& mInjector.isBrightnessModeAutomatic(mContentResolver)) {
mAmbientBrightnessStatsTracker.start();
mSensorRegistered = true;
@@ -825,11 +838,14 @@ public class BrightnessTracker {
pw.println(" mColorSamplingEnabled=" + mColorSamplingEnabled);
pw.println(" mNoFramesToSample=" + mNoFramesToSample);
pw.println(" mFrameRate=" + mFrameRate);
+ pw.println(" mIsDisplayActive=" + mIsDisplayActive);
+ pw.println(" mDisplayListenerRegistered=" + mDisplayListenerRegistered);
}
private void enableColorSampling() {
if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
|| !mInjector.isInteractive(mContext)
+ || !mIsDisplayActive
|| mColorSamplingEnabled
|| !mShouldCollectColorSample) {
return;
@@ -860,10 +876,6 @@ public class BrightnessTracker {
+ mNoFramesToSample + " frames, success=" + mColorSamplingEnabled);
}
}
- if (mColorSamplingEnabled && mDisplayListener == null) {
- mDisplayListener = new DisplayListener();
- mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
- }
}
private void disableColorSampling() {
@@ -872,10 +884,6 @@ public class BrightnessTracker {
}
mInjector.enableColorSampling(/* enable= */ false, /* noFrames= */ 0);
mColorSamplingEnabled = false;
- if (mDisplayListener != null) {
- mInjector.unRegisterDisplayListener(mContext, mDisplayListener);
- mDisplayListener = null;
- }
if (DEBUG) {
Slog.i(TAG, "turning off color sampling");
}
@@ -913,6 +921,25 @@ public class BrightnessTracker {
}
}
+ private boolean isDisplayActive() {
+ return Display.isActiveState(mInjector.getDisplayState(mContext));
+ }
+
+ private void startDisplayListener() {
+ if (!mDisplayListenerRegistered && mLightSensor != null && mInjector.isInteractive(mContext)
+ && mInjector.isBrightnessModeAutomatic(mContentResolver)) {
+ mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
+ mDisplayListenerRegistered = true;
+ }
+ }
+
+ private void stopDisplayListener() {
+ if (mDisplayListenerRegistered) {
+ mInjector.unregisterDisplayListener(mContext, mDisplayListener);
+ mDisplayListenerRegistered = false;
+ }
+ }
+
private final class SensorListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
@@ -941,6 +968,15 @@ public class BrightnessTracker {
public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
updateColorSampling();
+ boolean isDisplayActive = isDisplayActive();
+ if (mIsDisplayActive != isDisplayActive) {
+ mIsDisplayActive = isDisplayActive;
+ if (isDisplayActive) {
+ mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
+ } else {
+ mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
+ }
+ }
}
}
}
@@ -956,8 +992,10 @@ public class BrightnessTracker {
Slog.v(TAG, "settings change " + uri);
}
if (mInjector.isBrightnessModeAutomatic(mContentResolver)) {
+ mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
} else {
+ mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
}
}
@@ -980,8 +1018,10 @@ public class BrightnessTracker {
batteryLevelChanged(level, scale);
}
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
}
}
@@ -1023,7 +1063,12 @@ public class BrightnessTracker {
case MSG_SENSOR_CHANGED:
handleSensorChanged((Sensor) msg.obj);
break;
-
+ case MSG_START_DISPLAY_LISTENER:
+ startDisplayListener();
+ break;
+ case MSG_STOP_DISPLAY_LISTENER:
+ stopDisplayListener();
+ break;
}
}
}
@@ -1151,6 +1196,12 @@ public class BrightnessTracker {
return context.getSystemService(PowerManager.class).isInteractive();
}
+ public int getDisplayState(Context context) {
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ return display.getState();
+ }
+
public int getNightDisplayColorTemperature(Context context) {
return context.getSystemService(ColorDisplayManager.class)
.getNightDisplayColorTemperature();
@@ -1208,7 +1259,7 @@ public class BrightnessTracker {
displayManager.registerDisplayListener(listener, handler);
}
- public void unRegisterDisplayListener(Context context,
+ public void unregisterDisplayListener(Context context,
DisplayManager.DisplayListener listener) {
final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
displayManager.unregisterDisplayListener(listener);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index dc263c5a6b32..e10bdaab4b97 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <screenBrightnessDefault>0.65</screenBrightnessDefault>
* <powerThrottlingConfig>
* <brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>
- * <customAnimationRateSec>0.004</customAnimationRateSec>
+ * <customAnimationRate>0.004</customAnimationRate>
* <pollingWindowMaxMillis>30000</pollingWindowMaxMillis>
* <pollingWindowMinMillis>10000</pollingWindowMinMillis>
* <powerThrottlingMap>
@@ -377,6 +377,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </point>
* </map>
* </luxToBrightnessMapping>
+ * <idleStylusTimeoutMillis>10000</idleStylusTimeoutMillis>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -708,6 +709,10 @@ public class DisplayDeviceConfig {
private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+ // The default value to 0 which will signify that the stylus usage immediately stopped
+ // after it was started. This will make the system behave as if the stylus was never used
+ private static final int DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS = 0;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -754,9 +759,13 @@ public class DisplayDeviceConfig {
@Nullable
private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
+ private int mIdleStylusTimeoutMillis =
+ DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS;
+
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
+ private float mBrightnessDim = Float.NaN;
private float mBrightnessRampFastDecrease = Float.NaN;
private float mBrightnessRampFastIncrease = Float.NaN;
private float mBrightnessRampSlowDecrease = Float.NaN;
@@ -1080,7 +1089,7 @@ public class DisplayDeviceConfig {
*/
public float[] getNits() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mNits;
+ return mEvenDimmerBrightnessData.nits;
}
return mNits;
}
@@ -1093,7 +1102,7 @@ public class DisplayDeviceConfig {
@VisibleForTesting
public float[] getBacklight() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBacklight;
+ return mEvenDimmerBrightnessData.backlight;
}
return mBacklight;
}
@@ -1107,7 +1116,7 @@ public class DisplayDeviceConfig {
*/
public float getBacklightFromBrightness(float brightness) {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBrightnessToBacklight.interpolate(brightness);
+ return mEvenDimmerBrightnessData.brightnessToBacklight.interpolate(brightness);
}
return mBrightnessToBacklightSpline.interpolate(brightness);
}
@@ -1120,7 +1129,7 @@ public class DisplayDeviceConfig {
*/
public float getBrightnessFromBacklight(float backlight) {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBacklightToBrightness.interpolate(backlight);
+ return mEvenDimmerBrightnessData.backlightToBrightness.interpolate(backlight);
}
return mBacklightToBrightnessSpline.interpolate(backlight);
}
@@ -1131,7 +1140,7 @@ public class DisplayDeviceConfig {
*/
private Spline getBacklightToBrightnessSpline() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBacklightToBrightness;
+ return mEvenDimmerBrightnessData.backlightToBrightness;
}
return mBacklightToBrightnessSpline;
}
@@ -1144,11 +1153,11 @@ public class DisplayDeviceConfig {
*/
public float getNitsFromBacklight(float backlight) {
if (mEvenDimmerBrightnessData != null) {
- if (mEvenDimmerBrightnessData.mBacklightToNits == null) {
+ if (mEvenDimmerBrightnessData.backlightToNits == null) {
return INVALID_NITS;
}
backlight = Math.max(backlight, mBacklightMinimum);
- return mEvenDimmerBrightnessData.mBacklightToNits.interpolate(backlight);
+ return mEvenDimmerBrightnessData.backlightToNits.interpolate(backlight);
}
if (mBacklightToNitsSpline == null) {
@@ -1165,14 +1174,14 @@ public class DisplayDeviceConfig {
*/
public float getBacklightFromNits(float nits) {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mNitsToBacklight.interpolate(nits);
+ return mEvenDimmerBrightnessData.nitsToBacklight.interpolate(nits);
}
return mNitsToBacklightSpline.interpolate(nits);
}
private Spline getNitsToBacklightSpline() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mNitsToBacklight;
+ return mEvenDimmerBrightnessData.nitsToBacklight;
}
return mNitsToBacklightSpline;
}
@@ -1186,7 +1195,7 @@ public class DisplayDeviceConfig {
if (mEvenDimmerBrightnessData == null) {
return INVALID_NITS;
}
- return mEvenDimmerBrightnessData.mMinLuxToNits.interpolate(lux);
+ return mEvenDimmerBrightnessData.minLuxToNits.interpolate(lux);
}
/**
@@ -1197,7 +1206,7 @@ public class DisplayDeviceConfig {
if (mEvenDimmerBrightnessData == null) {
return PowerManager.BRIGHTNESS_MIN;
}
- return mEvenDimmerBrightnessData.mTransitionPoint;
+ return mEvenDimmerBrightnessData.transitionPoint;
}
/**
@@ -1268,12 +1277,30 @@ public class DisplayDeviceConfig {
*/
public float[] getBrightness() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBrightness;
+ return mEvenDimmerBrightnessData.brightness;
}
return mBrightness;
}
/**
+ * Return the minimum brightness on a scale of 0.0f - 1.0f
+ *
+ * @return minimum brightness
+ */
+ public float getBrightnessMinimum() {
+ return getBrightnessFromBacklight(mBacklightMinimum);
+ }
+
+ /**
+ * Return the maximum brightness on a scale of 0.0f - 1.0f
+ *
+ * @return maximum brightness
+ */
+ public float getBrightnessMaximum() {
+ return getBrightnessFromBacklight(mBacklightMaximum);
+ }
+
+ /**
* Return the default brightness on a scale of 0.0f - 1.0f
*
* @return default brightness
@@ -1282,6 +1309,15 @@ public class DisplayDeviceConfig {
return mBrightnessDefault;
}
+ /**
+ * Return the dim brightness on a scale of 0.0f - 1.0f
+ *
+ * @return dim brightness
+ */
+ public float getBrightnessDim() {
+ return mBrightnessDim;
+ }
+
public float getBrightnessRampFastDecrease() {
return mBrightnessRampFastDecrease;
}
@@ -1681,6 +1717,7 @@ public class DisplayDeviceConfig {
+ ", mBacklightMinimum=" + mBacklightMinimum
+ ", mBacklightMaximum=" + mBacklightMaximum
+ ", mBrightnessDefault=" + mBrightnessDefault
+ + ", mBrightnessDim=" + mBrightnessDim
+ ", mQuirks=" + mQuirks
+ "\n"
+ "mLuxThrottlingData=" + mLuxThrottlingData
@@ -1730,6 +1767,7 @@ public class DisplayDeviceConfig {
+ ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ + ", mIdleStylusTimeoutMillis= " + mIdleStylusTimeoutMillis
+ "\n"
+ "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
@@ -1897,6 +1935,7 @@ public class DisplayDeviceConfig {
mBacklightMinimum = PowerManager.BRIGHTNESS_MIN;
mBacklightMaximum = PowerManager.BRIGHTNESS_MAX;
mBrightnessDefault = BRIGHTNESS_DEFAULT;
+ mBrightnessDim = PowerManager.BRIGHTNESS_INVALID;
mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX;
mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX;
mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX;
@@ -1994,6 +2033,15 @@ public class DisplayDeviceConfig {
mBacklightMinimum = min;
mBacklightMaximum = max;
}
+ final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessDimFloat);
+ if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
+ mBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
+ mContext.getResources().getInteger(com.android.internal.R.integer
+ .config_screenBrightnessDim));
+ } else {
+ mBrightnessDim = dim;
+ }
}
private void loadBrightnessMap(DisplayConfiguration config) {
@@ -2184,11 +2232,11 @@ public class DisplayDeviceConfig {
return;
}
float lowestBrightnessCap = powerThrottlingCfg.getBrightnessLowestCapAllowed().floatValue();
- float customAnimationRateSec = powerThrottlingCfg.getCustomAnimationRateSec().floatValue();
+ float customAnimationRate = powerThrottlingCfg.getCustomAnimationRate().floatValue();
int pollingWindowMaxMillis = powerThrottlingCfg.getPollingWindowMaxMillis().intValue();
int pollingWindowMinMillis = powerThrottlingCfg.getPollingWindowMinMillis().intValue();
mPowerThrottlingConfigData = new PowerThrottlingConfigData(lowestBrightnessCap,
- customAnimationRateSec,
+ customAnimationRate,
pollingWindowMaxMillis,
pollingWindowMinMillis);
}
@@ -2389,10 +2437,19 @@ public class DisplayDeviceConfig {
loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
autoBrightness, getBacklightToBrightnessSpline());
+ loadIdleStylusTimeoutMillis(autoBrightness);
loadEnableAutoBrightness(autoBrightness);
}
/**
+ * Gets the timeout post the stylus usage after which the automatic brightness will be enabled
+ * again
+ */
+ public int getIdleStylusTimeoutMillis() {
+ return mIdleStylusTimeoutMillis;
+ }
+
+ /**
* Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
* the value from the display config, and if not present, falls back to config.xml.
*/
@@ -2617,13 +2674,13 @@ public class DisplayDeviceConfig {
List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
for (NonNegativeFloatToFloatPoint point : points) {
float lux = point.getFirst().floatValue();
- float maxBrightness = point.getSecond().floatValue();
- if (maxBrightness > hbmTransitionPoint) {
+ float maxBacklight = point.getSecond().floatValue();
+ if (maxBacklight > hbmTransitionPoint) {
Slog.wtf(TAG,
- "Invalid NBM config: maxBrightness is greater than hbm"
+ "Invalid NBM config: maxBacklight is greater than hbm"
+ ".transitionPoint. type="
- + type + "; lux=" + lux + "; maxBrightness="
- + maxBrightness);
+ + type + "; lux=" + lux + "; maxBacklight="
+ + maxBacklight);
continue;
}
if (luxToTransitionPointMap.containsKey(lux)) {
@@ -2632,8 +2689,7 @@ public class DisplayDeviceConfig {
+ lux);
continue;
}
- luxToTransitionPointMap.put(lux,
- getBrightnessFromBacklight(maxBrightness));
+ luxToTransitionPointMap.put(lux, getBrightnessFromBacklight(maxBacklight));
}
if (!luxToTransitionPointMap.isEmpty()) {
mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
@@ -2924,6 +2980,16 @@ public class DisplayDeviceConfig {
return levels;
}
+ private void loadIdleStylusTimeoutMillis(AutoBrightness autoBrightness) {
+ if (autoBrightness == null) {
+ return;
+ }
+ BigInteger idleStylusTimeoutMillis = autoBrightness.getIdleStylusTimeoutMillis();
+ if (idleStylusTimeoutMillis != null) {
+ mIdleStylusTimeoutMillis = idleStylusTimeoutMillis.intValue();
+ }
+ }
+
private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
// mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
// config.xml values if the autobrightness tag is not defined in the ddc file.
@@ -2985,16 +3051,16 @@ public class DisplayDeviceConfig {
/** Lowest brightness cap allowed for this device. */
public final float brightnessLowestCapAllowed;
/** Time take to animate brightness in seconds. */
- public final float customAnimationRateSec;
+ public final float customAnimationRate;
/** Time window for maximum polling power in milliseconds. */
public final int pollingWindowMaxMillis;
/** Time window for minimum polling power in milliseconds. */
public final int pollingWindowMinMillis;
public PowerThrottlingConfigData(float brightnessLowestCapAllowed,
- float customAnimationRateSec, int pollingWindowMaxMillis,
+ float customAnimationRate, int pollingWindowMaxMillis,
int pollingWindowMinMillis) {
this.brightnessLowestCapAllowed = brightnessLowestCapAllowed;
- this.customAnimationRateSec = customAnimationRateSec;
+ this.customAnimationRate = customAnimationRate;
this.pollingWindowMaxMillis = pollingWindowMaxMillis;
this.pollingWindowMinMillis = pollingWindowMinMillis;
}
@@ -3004,7 +3070,7 @@ public class DisplayDeviceConfig {
return "PowerThrottlingConfigData{"
+ "brightnessLowestCapAllowed: "
+ brightnessLowestCapAllowed
- + ", customAnimationRateSec: " + customAnimationRateSec
+ + ", customAnimationRate: " + customAnimationRate
+ ", pollingWindowMaxMillis: " + pollingWindowMaxMillis
+ ", pollingWindowMinMillis: " + pollingWindowMinMillis
+ "} ";
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index acf4db30ba93..3aaf4f6fe85a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,6 +26,7 @@ import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
import android.view.DisplayShape;
+import android.view.FrameRateCategoryRate;
import android.view.RoundedCorners;
import android.view.Surface;
@@ -254,6 +255,11 @@ final class DisplayDeviceInfo {
public static final int DIFF_MODE_ID = 1 << 7;
/**
+ * Diff result: The frame rate override list differs.
+ */
+ public static final int DIFF_FRAME_RATE_OVERRIDE = 1 << 8;
+
+ /**
* Diff result: Catch-all for "everything changed"
*/
public static final int DIFF_EVERYTHING = 0XFFFFFFFF;
@@ -292,6 +298,23 @@ final class DisplayDeviceInfo {
*/
public float renderFrameRate;
+
+ /**
+ * If {@code true}, this Display supports adaptive refresh rates.
+ * @see android.view.DisplayInfo#hasArrSupport for more details.
+ */
+ public boolean hasArrSupport;
+
+ /**
+ * Represents frame rate for the FrameRateCategory Normal and High.
+ * @see android.view.Display#getSuggestedFrameRate(int) for more details.
+ */
+ public FrameRateCategoryRate frameRateCategoryRate;
+ /**
+ * All the refresh rates supported for the default display mode.
+ */
+ public float[] supportedRefreshRates = new float[0];
+
/**
* The default mode of the display.
*/
@@ -454,6 +477,7 @@ final class DisplayDeviceInfo {
public float brightnessMinimum;
public float brightnessMaximum;
public float brightnessDefault;
+ public float brightnessDim;
// NaN means unsupported
public float hdrSdrRatio = Float.NaN;
@@ -510,6 +534,9 @@ final class DisplayDeviceInfo {
if (modeId != other.modeId) {
diff |= DIFF_MODE_ID;
}
+ if (!Arrays.equals(frameRateOverrides, other.frameRateOverrides)) {
+ diff |= DIFF_FRAME_RATE_OVERRIDE;
+ }
if (!Objects.equals(name, other.name)
|| !Objects.equals(uniqueId, other.uniqueId)
|| width != other.width
@@ -533,14 +560,16 @@ final class DisplayDeviceInfo {
|| !Objects.equals(deviceProductInfo, other.deviceProductInfo)
|| ownerUid != other.ownerUid
|| !Objects.equals(ownerPackageName, other.ownerPackageName)
- || !Arrays.equals(frameRateOverrides, other.frameRateOverrides)
|| !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
|| !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
- || !BrightnessSynchronizer.floatEquals(brightnessDefault,
- other.brightnessDefault)
+ || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault)
+ || !BrightnessSynchronizer.floatEquals(brightnessDim, other.brightnessDim)
|| !Objects.equals(roundedCorners, other.roundedCorners)
|| installOrientation != other.installOrientation
- || !Objects.equals(displayShape, other.displayShape)) {
+ || !Objects.equals(displayShape, other.displayShape)
+ || hasArrSupport != other.hasArrSupport
+ || !Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
+ || !Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)) {
diff |= DIFF_OTHER;
}
return diff;
@@ -558,6 +587,9 @@ final class DisplayDeviceInfo {
height = other.height;
modeId = other.modeId;
renderFrameRate = other.renderFrameRate;
+ hasArrSupport = other.hasArrSupport;
+ frameRateCategoryRate = other.frameRateCategoryRate;
+ supportedRefreshRates = other.supportedRefreshRates;
defaultModeId = other.defaultModeId;
userPreferredModeId = other.userPreferredModeId;
supportedModes = other.supportedModes;
@@ -587,6 +619,7 @@ final class DisplayDeviceInfo {
brightnessMinimum = other.brightnessMinimum;
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
+ brightnessDim = other.brightnessDim;
hdrSdrRatio = other.hdrSdrRatio;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
@@ -602,6 +635,9 @@ final class DisplayDeviceInfo {
sb.append(width).append(" x ").append(height);
sb.append(", modeId ").append(modeId);
sb.append(", renderFrameRate ").append(renderFrameRate);
+ sb.append(", hasArrSupport ").append(hasArrSupport);
+ sb.append(", frameRateCategoryRate ").append(frameRateCategoryRate);
+ sb.append(", supportedRefreshRates ").append(Arrays.toString(supportedRefreshRates));
sb.append(", defaultModeId ").append(defaultModeId);
sb.append(", userPreferredModeId ").append(userPreferredModeId);
sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
@@ -638,6 +674,7 @@ final class DisplayDeviceInfo {
sb.append(", brightnessMinimum ").append(brightnessMinimum);
sb.append(", brightnessMaximum ").append(brightnessMaximum);
sb.append(", brightnessDefault ").append(brightnessDefault);
+ sb.append(", brightnessDim ").append(brightnessDim);
sb.append(", hdrSdrRatio ").append(hdrSdrRatio);
if (roundedCorners != null) {
sb.append(", roundedCorners ").append(roundedCorners);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 086f8a94d9b8..5f7bc4effa1b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -27,6 +27,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.server.display.DisplayManagerService.SyncRoot;
import com.android.server.display.utils.DebugUtils;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -177,18 +178,22 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener {
"handleDisplayDeviceChanged");
}
int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
- if (diff == DisplayDeviceInfo.DIFF_STATE) {
+ if (diff == 0) {
+ Slog.i(TAG, "Display device same: " + info);
+ } else if (diff == DisplayDeviceInfo.DIFF_STATE) {
Slog.i(TAG, "Display device changed state: \"" + info.name
+ "\", " + Display.stateToString(info.state));
} else if (diff == DisplayDeviceInfo.DIFF_ROTATION) {
Slog.i(TAG, "Display device rotated: \"" + info.name
+ "\", " + Surface.rotationToString(info.rotation));
- } else if (diff
- == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) {
+ } else if ((diff &
+ (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS
+ | DisplayDeviceInfo.DIFF_FRAME_RATE_OVERRIDE)) != 0) {
Slog.i(TAG, "Display device changed render timings: \"" + info.name
+ "\", renderFrameRate=" + info.renderFrameRate
+ ", presentationDeadlineNanos=" + info.presentationDeadlineNanos
- + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos);
+ + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos
+ + ", frameRateOverrides=" + Arrays.toString(info.frameRateOverrides));
} else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) {
if (DEBUG) {
Slog.i(TAG, "Display device changed committed state: \"" + info.name
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index 2dcd5ccaf557..f73b66c78fce 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -87,4 +87,14 @@ public class DisplayGroup {
int getIdLocked(int index) {
return mDisplays.get(index).getDisplayIdLocked();
}
+
+ /** Returns the IDs of the {@link LogicalDisplay}s belonging to the DisplayGroup. */
+ int[] getIdsLocked() {
+ final int numDisplays = mDisplays.size();
+ final int[] displayIds = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayIds[i] = mDisplays.get(i).getDisplayIdLocked();
+ }
+ return displayIds;
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ae33b83b49dc..452dc5f97d12 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_MIRROR_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
@@ -25,7 +26,7 @@ import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
-import static android.hardware.display.DisplayManager.EventsMask;
+import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -45,9 +46,12 @@ import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPOR
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.text.TextUtils.formatSimple;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
@@ -68,6 +72,7 @@ import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -94,6 +99,7 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -116,6 +122,7 @@ import android.os.IBinder.DeathRecipient;
import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -173,6 +180,7 @@ import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.notifications.DisplayNotificationManager;
+import com.android.server.display.plugin.PluginManager;
import com.android.server.display.utils.DebugUtils;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
@@ -279,6 +287,8 @@ public final class DisplayManagerService extends SystemService {
private InputManagerInternal mInputManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
private final UidImportanceListener mUidImportanceListener = new UidImportanceListener();
+ private final DisplayFrozenProcessListener mDisplayFrozenProcessListener;
+
@Nullable
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@@ -321,6 +331,12 @@ public final class DisplayManagerService extends SystemService {
@GuardedBy("mSyncRoot")
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
+ // All callback records indexed by [uid][pid], for fast lookup by uid.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mSyncRoot")
+ private final SparseArray<SparseArray<CallbackRecord>> mCallbackRecordByPidByUid =
+ new SparseArray<>();
+
/**
* All {@link IVirtualDevice} and {@link DisplayWindowPolicyController}s indexed by
* {@link DisplayInfo#displayId}.
@@ -472,6 +488,7 @@ public final class DisplayManagerService extends SystemService {
// Pending callback records indexed by calling process uid and pid.
// Must be used outside of the lock mSyncRoot and should be self-locked.
+ // This is only used when {@link deferDisplayEventsWhenFrozen()} is false.
@GuardedBy("mPendingCallbackSelfLocked")
private final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =
new SparseArray<>();
@@ -519,6 +536,8 @@ public final class DisplayManagerService extends SystemService {
private final boolean mExtraDisplayEventLogging;
private final String mExtraDisplayLoggingPackageName;
+ private boolean mMirrorBuiltInDisplay;
+
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -570,6 +589,7 @@ public final class DisplayManagerService extends SystemService {
private final DisplayNotificationManager mDisplayNotificationManager;
private final ExternalDisplayStatsService mExternalDisplayStatsService;
+ private final PluginManager mPluginManager;
// Manages the relative placement of extended displays
@Nullable
@@ -611,6 +631,7 @@ public final class DisplayManagerService extends SystemService {
mFlags = injector.getFlags();
mHandler = new DisplayManagerHandler(displayThreadLooper);
mHandlerExecutor = new HandlerExecutor(mHandler);
+ mDisplayFrozenProcessListener = new DisplayFrozenProcessListener();
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -644,15 +665,19 @@ public final class DisplayManagerService extends SystemService {
mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
- mExternalDisplayStatsService = new ExternalDisplayStatsService(mContext, mHandler);
+ mExternalDisplayStatsService = new ExternalDisplayStatsService(mContext, mHandler,
+ this::isExtendedDisplayEnabled);
mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
mExternalDisplayStatsService);
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
if (mFlags.isDisplayTopologyEnabled()) {
- mDisplayTopologyCoordinator = new DisplayTopologyCoordinator();
+ mDisplayTopologyCoordinator =
+ new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled,
+ this::deliverTopologyUpdate, new HandlerExecutor(mHandler), mSyncRoot);
} else {
mDisplayTopologyCoordinator = null;
}
+ mPluginManager = new PluginManager(mContext, mFlags);
}
public void setupSchedulerPolicies() {
@@ -723,6 +748,7 @@ public final class DisplayManagerService extends SystemService {
mLogicalDisplayMapper.onBootCompleted();
mDisplayNotificationManager.onBootCompleted();
mExternalDisplayPolicy.onBootCompleted();
+ mPluginManager.onBootCompleted();
}
}
@@ -760,7 +786,11 @@ public final class DisplayManagerService extends SystemService {
}
dpc.onSwitchUser(newUserId, userSerial, newBrightness);
});
- handleSettingsChange();
+ handleMinimalPostProcessingAllowedSettingChange();
+
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
}
}
@@ -803,12 +833,15 @@ public final class DisplayManagerService extends SystemService {
// relevant configuration should be in place.
recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
- updateSettingsLocked();
+ updateMinimalPostProcessingAllowedSettingLocked();
updateUserDisabledHdrTypesFromSettingsLocked();
updateUserPreferredDisplayModeSettingsLocked();
if (mIsHdrOutputControlEnabled) {
updateHdrConversionModeSettingsLocked();
}
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
}
mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -1034,10 +1067,14 @@ public final class DisplayManagerService extends SystemService {
private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
@Override
public void onUidImportance(int uid, int importance) {
- onUidImportanceInternal(uid, importance);
+ if (deferDisplayEventsWhenFrozen()) {
+ onUidImportanceFlagged(uid, importance);
+ } else {
+ onUidImportanceUnflagged(uid, importance);
+ }
}
- private void onUidImportanceInternal(int uid, int importance) {
+ private void onUidImportanceUnflagged(int uid, int importance) {
synchronized (mPendingCallbackSelfLocked) {
if (importance >= IMPORTANCE_GONE) {
// Clean up as the app is already gone
@@ -1068,6 +1105,83 @@ public final class DisplayManagerService extends SystemService {
mPendingCallbackSelfLocked.delete(uid);
}
}
+
+ private void onUidImportanceFlagged(int uid, int importance) {
+ final boolean cached = (importance >= IMPORTANCE_CACHED);
+ List<CallbackRecord> readyCallbackRecords = null;
+ synchronized (mSyncRoot) {
+ final SparseArray<CallbackRecord> procs = mCallbackRecordByPidByUid.get(uid);
+ if (procs == null) {
+ return;
+ }
+ if (cached) {
+ setCachedLocked(procs);
+ } else {
+ readyCallbackRecords = setUncachedLocked(procs);
+ }
+ }
+ if (readyCallbackRecords != null) {
+ // Attempt to dispatch pending events if the UID is coming out of cached state.
+ for (int i = 0; i < readyCallbackRecords.size(); i++) {
+ readyCallbackRecords.get(i).dispatchPending();
+ }
+ }
+ }
+
+ // Set all processes in the list to cached.
+ @GuardedBy("mSyncRoot")
+ private void setCachedLocked(SparseArray<CallbackRecord> procs) {
+ for (int i = 0; i < procs.size(); i++) {
+ final CallbackRecord cb = procs.valueAt(i);
+ if (cb != null) {
+ cb.setCached(true);
+ }
+ }
+ }
+
+ // Set all processes to uncached and return the list of processes that were modified.
+ @GuardedBy("mSyncRoot")
+ private List<CallbackRecord> setUncachedLocked(SparseArray<CallbackRecord> procs) {
+ ArrayList<CallbackRecord> ready = null;
+ for (int i = 0; i < procs.size(); i++) {
+ final CallbackRecord cb = procs.valueAt(i);
+ if (cb != null) {
+ if (cb.setCached(false)) {
+ if (ready == null) ready = new ArrayList<>();
+ ready.add(cb);
+ }
+ }
+ }
+ return ready;
+ }
+ }
+
+ private class DisplayFrozenProcessListener
+ implements ActivityManagerInternal.FrozenProcessListener {
+ public void onProcessFrozen(int pid) {
+ synchronized (mSyncRoot) {
+ CallbackRecord callback = mCallbacks.get(pid);
+ if (callback == null) {
+ return;
+ }
+ callback.setFrozen(true);
+ }
+ }
+
+ public void onProcessUnfrozen(int pid) {
+ // First, see if there is a callback associated with this pid. If there's no
+ // callback, then there is nothing to do.
+ CallbackRecord callback;
+ synchronized (mSyncRoot) {
+ callback = mCallbacks.get(pid);
+ if (callback == null) {
+ return;
+ }
+ callback.setFrozen(false);
+ }
+ // Attempt to dispatch pending events if the process is coming out of frozen.
+ callback.dispatchPending();
+ }
}
private class SettingsObserver extends ContentObserver {
@@ -1077,27 +1191,61 @@ public final class DisplayManagerService extends SystemService {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED), false, this);
+
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ MIRROR_BUILT_IN_DISPLAY), false, this, UserHandle.USER_ALL);
+ }
}
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange();
+ if (Settings.Secure.getUriFor(
+ Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED).equals(uri)) {
+ handleMinimalPostProcessingAllowedSettingChange();
+ return;
+ }
+
+ if (Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY).equals(uri)) {
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
+ return;
+ }
}
}
- private void handleSettingsChange() {
+ private void handleMinimalPostProcessingAllowedSettingChange() {
synchronized (mSyncRoot) {
- updateSettingsLocked();
+ updateMinimalPostProcessingAllowedSettingLocked();
scheduleTraversalLocked(false);
}
}
- private void updateSettingsLocked() {
+ private void updateMinimalPostProcessingAllowedSettingLocked() {
setMinimalPostProcessingAllowed(Settings.Secure.getIntForUser(
mContext.getContentResolver(), Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED,
1, UserHandle.USER_CURRENT) != 0);
}
+ private void updateMirrorBuiltInDisplaySettingLocked() {
+ if (!mFlags.isDisplayContentModeManagementEnabled()) {
+ Slog.e(TAG, "MirrorBuiltInDisplay setting shouldn't be updated when the flag is off.");
+ return;
+ }
+
+ synchronized (mSyncRoot) {
+ ContentResolver resolver = mContext.getContentResolver();
+ final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver,
+ MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0;
+ if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) {
+ return;
+ }
+ mMirrorBuiltInDisplay = mirrorBuiltInDisplay;
+ }
+ }
+
private void restoreResolutionFromBackup() {
int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.SCREEN_RESOLUTION_MODE,
@@ -1295,16 +1443,16 @@ public final class DisplayManagerService extends SystemService {
}
private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid,
- int callingUid, @EventsMask long eventsMask) {
+ int callingUid, @InternalEventFlag long internalEventFlagsMask) {
synchronized (mSyncRoot) {
CallbackRecord record = mCallbacks.get(callingPid);
if (record != null) {
- record.updateEventsMask(eventsMask);
+ record.updateEventFlagsMask(internalEventFlagsMask);
return;
}
- record = new CallbackRecord(callingPid, callingUid, callback, eventsMask);
+ record = new CallbackRecord(callingPid, callingUid, callback, internalEventFlagsMask);
try {
IBinder binder = callback.asBinder();
binder.linkToDeath(record, 0);
@@ -1314,12 +1462,29 @@ public final class DisplayManagerService extends SystemService {
}
mCallbacks.put(callingPid, record);
+ if (deferDisplayEventsWhenFrozen()) {
+ SparseArray<CallbackRecord> uidPeers = mCallbackRecordByPidByUid.get(record.mUid);
+ if (uidPeers == null) {
+ uidPeers = new SparseArray<CallbackRecord>();
+ mCallbackRecordByPidByUid.put(record.mUid, uidPeers);
+ }
+ uidPeers.put(record.mPid, record);
+ }
}
}
private void onCallbackDied(CallbackRecord record) {
synchronized (mSyncRoot) {
mCallbacks.remove(record.mPid);
+ if (deferDisplayEventsWhenFrozen()) {
+ SparseArray<CallbackRecord> uidPeers = mCallbackRecordByPidByUid.get(record.mUid);
+ if (uidPeers != null) {
+ uidPeers.remove(record.mPid);
+ if (uidPeers.size() == 0) {
+ mCallbackRecordByPidByUid.remove(record.mUid);
+ }
+ }
+ }
stopWifiDisplayScanLocked(record);
}
}
@@ -1549,33 +1714,49 @@ public final class DisplayManagerService extends SystemService {
return false;
}
+ private boolean hasVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_VIDEO_OUTPUT, func)
+ || hasSecureVideoOutputPermission(func);
+ }
+
+ private boolean hasSecureVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, func);
+ }
+
+ private boolean canCreateMirrorDisplays(IVirtualDevice virtualDevice) {
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ return checkCallingPermission(ADD_MIRROR_DISPLAY, "canCreateMirrorDisplays");
+ }
+ try {
+ return virtualDevice != null && virtualDevice.canCreateMirrorDisplays();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query virtual device for permissions", e);
+ return false;
+ }
+ }
+
private boolean canProjectVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
}
- if (checkCallingPermission(CAPTURE_VIDEO_OUTPUT, "canProjectVideo()")) {
- return true;
+ try {
+ return projection.canProjectVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
}
- return canProjectSecureVideo(projection);
}
private boolean canProjectSecureVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectSecureVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
+ }
+ try {
+ return projection.canProjectSecureVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
}
- return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, "canProjectSecureVideo()");
}
private boolean checkCallingPermission(String permission, String func) {
@@ -1641,10 +1822,11 @@ public final class DisplayManagerService extends SystemService {
flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
}
// Put the display in the virtual device's display group only if it's not a mirror display,
- // and if it doesn't need its own display group. So effectively, mirror displays go into the
- // default display group.
+ // it is a trusted display, and it doesn't need its own display group. So effectively,
+ // mirror and untrusted displays go into the default display group.
if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0
+ && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == VIRTUAL_DISPLAY_FLAG_TRUSTED
&& virtualDevice != null) {
flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
}
@@ -1681,8 +1863,9 @@ public final class DisplayManagerService extends SystemService {
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
// Only a valid media projection or a virtual device can create a mirror virtual
// display.
- if (!canProjectVideo(projection) && virtualDevice == null) {
- throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ if (!canProjectVideo(projection) && !canCreateMirrorDisplays(virtualDevice)
+ && !hasVideoOutputPermission("createVirtualDisplayInternal")) {
+ throw new SecurityException("Requires ADD_MIRROR_DISPLAY, CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
+ "display. In order to create a virtual display that does not perform "
@@ -1691,7 +1874,8 @@ public final class DisplayManagerService extends SystemService {
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
- if (!canProjectSecureVideo(projection)) {
+ if (!canProjectSecureVideo(projection)
+ && !hasSecureVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+ "or an appropriate MediaProjection token to create a "
+ "secure virtual display.");
@@ -1718,9 +1902,7 @@ public final class DisplayManagerService extends SystemService {
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
- // The virtualDevice instance has been validated above using isValidVirtualDevice
- if (virtualDevice == null
- && !checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
+ if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ "create a virtual display which is not in the default DisplayGroup.");
}
@@ -1760,6 +1942,7 @@ public final class DisplayManagerService extends SystemService {
final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
packageName, callingUid, virtualDisplayConfig);
+ boolean shouldClearDisplayWindowSettings = false;
if (virtualDisplayConfig.isHomeSupported()) {
if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {
Slog.w(TAG, "Display created with home support but lacks "
@@ -1771,6 +1954,18 @@ public final class DisplayManagerService extends SystemService {
} else {
mWindowManagerInternal.setHomeSupportedOnDisplay(displayUniqueId,
Display.TYPE_VIRTUAL, true);
+ shouldClearDisplayWindowSettings = true;
+ }
+ }
+
+ if (virtualDisplayConfig.isIgnoreActivitySizeRestrictions()) {
+ if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {
+ Slog.w(TAG, "Display created to ignore activity size restrictions, "
+ + "but lacks VIRTUAL_DISPLAY_FLAG_TRUSTED, ignoring the request.");
+ } else {
+ mWindowManagerInternal.setIgnoreActivitySizeRestrictionsOnDisplay(
+ displayUniqueId, Display.TYPE_VIRTUAL, true);
+ shouldClearDisplayWindowSettings = true;
}
}
@@ -1793,8 +1988,7 @@ public final class DisplayManagerService extends SystemService {
}
}
- if (displayId == Display.INVALID_DISPLAY && virtualDisplayConfig.isHomeSupported()
- && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+ if (displayId == Display.INVALID_DISPLAY && shouldClearDisplayWindowSettings) {
// Failed to create the virtual display, so we should clean up the WM settings
// because it won't receive the onDisplayRemoved callback.
mWindowManagerInternal.clearDisplaySettings(displayUniqueId, Display.TYPE_VIRTUAL);
@@ -1848,7 +2042,7 @@ public final class DisplayManagerService extends SystemService {
// handles stopping the projection.
Slog.w(TAG, "Content Recording: failed to start mirroring - "
+ "releasing virtual display " + displayId);
- releaseVirtualDisplayInternal(callback.asBinder());
+ releaseVirtualDisplayInternal(callback.asBinder(), callingUid);
return Display.INVALID_DISPLAY;
} else if (projection != null) {
// Indicate that this projection has been used to record, and can't be used
@@ -1937,7 +2131,7 @@ public final class DisplayManagerService extends SystemService {
// Something weird happened and the logical display was not created.
Slog.w(TAG, "Rejecting request to create virtual display "
+ "because the logical display was not created.");
- mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder());
+ mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder(), callingUid);
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
return -1;
@@ -1964,14 +2158,14 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void releaseVirtualDisplayInternal(IBinder appToken) {
+ private void releaseVirtualDisplayInternal(IBinder appToken, int callingUid) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
return;
}
DisplayDevice device =
- mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken, callingUid);
Slog.d(TAG, "Virtual Display: Display Device released");
if (device != null) {
// TODO: multi-display - handle virtual displays the same as other display adapters.
@@ -1981,16 +2175,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
- synchronized (mSyncRoot) {
- if (mVirtualDisplayAdapter == null) {
- return;
- }
-
- mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
- }
- }
-
private void setVirtualDisplayRotationInternal(IBinder appToken,
@Surface.Rotation int rotation) {
int displayId;
@@ -2153,6 +2337,17 @@ public final class DisplayManagerService extends SystemService {
updateLogicalDisplayState(display);
}
+ private boolean isExtendedDisplayEnabled() {
+ try {
+ return 0 != Settings.Global.getInt(
+ mContext.getContentResolver(),
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0);
+ } catch (Throwable e) {
+ // Some services might not be initialised yet to be able to call getInt
+ return false;
+ }
+ }
+
@SuppressLint("AndroidFrameworkRequiresPermission")
private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
@@ -2171,6 +2366,14 @@ public final class DisplayManagerService extends SystemService {
updateLogicalDisplayState(display);
mExternalDisplayPolicy.handleLogicalDisplayAddedLocked(display);
+
+ if (mFlags.isApplyDisplayChangedDuringDisplayAddedEnabled()) {
+ applyDisplayChangedLocked(display);
+ }
+
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.onDisplayAdded(display.getDisplayInfoLocked());
+ }
}
private void handleLogicalDisplayChangedLocked(@NonNull LogicalDisplay display) {
@@ -2258,6 +2461,9 @@ public final class DisplayManagerService extends SystemService {
} else {
releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
}
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.onDisplayRemoved(display.getDisplayIdLocked());
+ }
Slog.i(TAG, "Logical display removed: " + display.getDisplayIdLocked());
}
@@ -2305,6 +2511,15 @@ public final class DisplayManagerService extends SystemService {
DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED);
}
+ private void handleLogicalDisplayRefreshRateChangedLocked(@NonNull LogicalDisplay display) {
+ sendDisplayEventIfEnabledLocked(display,
+ DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+ }
+
+ private void handleLogicalDisplayStateChangedLocked(@NonNull LogicalDisplay display) {
+ sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED);
+ }
+
private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
.mDisplayDeviceConfig);
@@ -3296,12 +3511,16 @@ public final class DisplayManagerService extends SystemService {
// After releasing the lock, send the notifications out.
for (int i = 0; i < mTempCallbacks.size(); i++) {
CallbackRecord callbackRecord = mTempCallbacks.get(i);
- deliverEventInternal(callbackRecord, displayId, event);
+ if (deferDisplayEventsWhenFrozen()) {
+ deliverEventFlagged(callbackRecord, displayId, event);
+ } else {
+ deliverEventUnflagged(callbackRecord, displayId, event);
+ }
}
mTempCallbacks.clear();
}
- private void deliverEventInternal(CallbackRecord callbackRecord, int displayId, int event) {
+ private void deliverEventUnflagged(CallbackRecord callbackRecord, int displayId, int event) {
final int uid = callbackRecord.mUid;
final int pid = callbackRecord.mPid;
if (isUidCached(uid)) {
@@ -3330,6 +3549,32 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private void deliverEventFlagged(CallbackRecord callbackRecord, int displayId, int event) {
+ callbackRecord.notifyDisplayEventAsync(displayId, event);
+ }
+
+ private void deliverTopologyUpdate(DisplayTopology topology) {
+ if (DEBUG) {
+ Slog.d(TAG, "Delivering topology update");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
+ }
+
+ // Grab the lock and copy the callbacks.
+ List<CallbackRecord> callbacks = new ArrayList<>();
+ synchronized (mSyncRoot) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ callbacks.add(mCallbacks.valueAt(i));
+ }
+ }
+
+ // After releasing the lock, send the notifications out.
+ for (CallbackRecord callback : callbacks) {
+ callback.notifyTopologyUpdateAsync(topology);
+ }
+ }
+
private boolean extraLogging(String packageName) {
return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
}
@@ -3377,10 +3622,21 @@ public final class DisplayManagerService extends SystemService {
private void dumpInternal(PrintWriter pw) {
pw.println("DISPLAY MANAGER (dumpsys display)");
BrightnessTracker brightnessTrackerLocal;
+ SparseArray<DisplayPowerController> displayPowerControllersLocal = new SparseArray<>();
+ int displayPowerControllerCount;
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.increaseIndent();
synchronized (mSyncRoot) {
brightnessTrackerLocal = mBrightnessTracker;
+ displayPowerControllerCount = mDisplayPowerControllers.size();
+ for (int i = 0; i < displayPowerControllerCount; i++) {
+ displayPowerControllersLocal.put(
+ mDisplayPowerControllers.keyAt(i), mDisplayPowerControllers.valueAt(i));
+ }
+
pw.println(" mSafeMode=" + mSafeMode);
pw.println(" mPendingTraversal=" + mPendingTraversal);
pw.println(" mViewports=" + mViewports);
@@ -3419,9 +3675,6 @@ public final class DisplayManagerService extends SystemService {
pw.println(" Display SdrBrightness=" + brightnessPair.sdrBrightness);
}
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- ipw.increaseIndent();
-
pw.println();
pw.println("Display Adapters: size=" + mDisplayAdapters.size());
pw.println("------------------------");
@@ -3446,16 +3699,7 @@ public final class DisplayManagerService extends SystemService {
pw.println("Callbacks: size=" + callbackCount);
pw.println("-----------------");
for (int i = 0; i < callbackCount; i++) {
- CallbackRecord callback = mCallbacks.valueAt(i);
- pw.println(" " + i + ": mPid=" + callback.mPid
- + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
- }
-
- final int displayPowerControllerCount = mDisplayPowerControllers.size();
- pw.println();
- pw.println("Display Power Controllers: size=" + displayPowerControllerCount);
- for (int i = 0; i < displayPowerControllerCount; i++) {
- mDisplayPowerControllers.valueAt(i).dump(pw);
+ pw.println(" " + i + ": " + mCallbacks.valueAt(i).dump());
}
pw.println();
@@ -3470,6 +3714,12 @@ public final class DisplayManagerService extends SystemService {
mDisplayWindowPolicyControllers.valueAt(i).second.dump(" ", pw);
}
}
+ pw.println();
+ pw.println("Display Power Controllers: size=" + displayPowerControllerCount);
+ for (int i = 0; i < displayPowerControllerCount; i++) {
+ displayPowerControllersLocal.valueAt(i).dump(pw);
+ }
+
if (brightnessTrackerLocal != null) {
pw.println();
brightnessTrackerLocal.dump(pw);
@@ -3487,6 +3737,8 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mDisplayTopologyCoordinator.dump(pw);
}
+ pw.println();
+ mPluginManager.dump(ipw);
pw.println();
mFlags.dump(pw);
@@ -3823,6 +4075,12 @@ public final class DisplayManagerService extends SystemService {
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED:
handleLogicalDisplayDisconnectedLocked(display);
break;
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
+ handleLogicalDisplayRefreshRateChangedLocked(display);
+ break;
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED:
+ handleLogicalDisplayStateChangedLocked(display);
+ break;
}
}
@@ -3843,24 +4101,95 @@ public final class DisplayManagerService extends SystemService {
public final int mPid;
public final int mUid;
private final IDisplayManagerCallback mCallback;
- private @EventsMask AtomicLong mEventsMask;
+ private @InternalEventFlag AtomicLong mInternalEventFlagsMask;
private final String mPackageName;
public boolean mWifiDisplayScanRequested;
+ // A single pending event.
+ private record Event(int displayId, @DisplayEvent int event) { };
+
+ // The list of pending events. This is null until there is a pending event to be saved.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mCallback")
+ private ArrayList<Event> mPendingEvents;
+
+ // Process states: a process is ready to receive events if it is neither cached nor
+ // frozen.
+ @GuardedBy("mCallback")
+ private boolean mCached;
+ @GuardedBy("mCallback")
+ private boolean mFrozen;
+
CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
- @EventsMask long eventsMask) {
+ @InternalEventFlag long internalEventFlagsMask) {
mPid = pid;
mUid = uid;
mCallback = callback;
- mEventsMask = new AtomicLong(eventsMask);
+ mInternalEventFlagsMask = new AtomicLong(internalEventFlagsMask);
+ mCached = false;
+ mFrozen = false;
+
+ if (deferDisplayEventsWhenFrozen()) {
+ // Some CallbackRecords are registered very early in system boot, before
+ // mActivityManagerInternal is initialized. If mActivityManagerInternal is null,
+ // do not register the frozen process listener. However, do verify that all such
+ // registrations are for the self pid (which can never be frozen, so the frozen
+ // process listener does not matter).
+ if (mActivityManagerInternal != null) {
+ mActivityManagerInternal.addFrozenProcessListener(pid, mHandlerExecutor,
+ mDisplayFrozenProcessListener);
+ } else if (Process.myPid() != pid) {
+ Slog.e(TAG, "DisplayListener registered too early");
+ }
+ }
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
mPackageName = packageNames == null ? null : packageNames[0];
}
- public void updateEventsMask(@EventsMask long eventsMask) {
- mEventsMask.set(eventsMask);
+ public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
+ mInternalEventFlagsMask.set(internalEventFlag);
+ }
+
+ /**
+ * Return true if the process can accept events.
+ */
+ @GuardedBy("mCallback")
+ private boolean isReadyLocked() {
+ return !mCached && !mFrozen;
+ }
+
+ /**
+ * Return true if the process is now ready and has pending events to be delivered.
+ */
+ @GuardedBy("mCallback")
+ private boolean hasPendingAndIsReadyLocked() {
+ return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty();
+ }
+
+ /**
+ * Set the frozen flag for this process. Return true if the process is now ready to
+ * receive events and there are pending events to be delivered.
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ */
+ public boolean setFrozen(boolean frozen) {
+ synchronized (mCallback) {
+ mFrozen = frozen;
+ return hasPendingAndIsReadyLocked();
+ }
+ }
+
+ /**
+ * Set the cached flag for this process. Return true if the process is now ready to
+ * receive events and there are pending events to be delivered.
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ */
+ public boolean setCached(boolean cached) {
+ synchronized (mCallback) {
+ mCached = cached;
+ return hasPendingAndIsReadyLocked();
+ }
}
@Override
@@ -3878,23 +4207,40 @@ public final class DisplayManagerService extends SystemService {
/**
* @return {@code false} if RemoteException happens; otherwise {@code true} for
* success. This returns true even if the event was deferred because the remote client is
- * cached.
+ * cached or frozen.
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
- if (!shouldSendEvent(event)) {
+ if (!shouldSendDisplayEvent(event)) {
if (extraLogging(mPackageName)) {
Slog.i(TAG,
- "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
+ "Not sending displayEvent: " + event + " due to mask:"
+ + mInternalEventFlagsMask);
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
Trace.instant(Trace.TRACE_TAG_POWER,
- "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
- + mEventsMask);
+ "notifyDisplayEventAsync#notSendingEvent=" + event
+ + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
}
// The client is not interested in this event, so do nothing.
return true;
}
+ if (deferDisplayEventsWhenFrozen()) {
+ synchronized (mCallback) {
+ // Add the new event to the pending list if the client frozen or cached (not
+ // ready) or if there are existing pending events. The latter condition
+ // occurs as the client is transitioning to ready but pending events have not
+ // been dispatched. The new event must be added to the pending list to
+ // preserve event ordering.
+ if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) {
+ // The client is interested in the event but is not ready to receive it.
+ // Put the event on the pending list.
+ addDisplayEvent(displayId, event);
+ return true;
+ }
+ }
+ }
+
return transmitDisplayEvent(displayId, event);
}
@@ -3918,31 +4264,157 @@ public final class DisplayManagerService extends SystemService {
/**
* Return true if the client is interested in this event.
*/
- private boolean shouldSendEvent(@DisplayEvent int event) {
- final long mask = mEventsMask.get();
+ private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
+ final long mask = mInternalEventFlagsMask.get();
switch (event) {
case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
- return (mask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0;
+ return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
- return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0;
+ return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
- return (mask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0;
+ return (mask
+ & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)
+ != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
- return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
+ return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED) != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
- return (mask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0;
+ return (mask
+ & DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED)
+ != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED:
// fallthrough
case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
- return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0;
+ return (mask
+ & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+ != 0;
+ case DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED:
+ return (mask
+ & DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE) != 0;
+ case DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED:
+ return (mask & DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_STATE) != 0;
default:
// This should never happen.
Slog.e(TAG, "Unknown display event " + event);
return true;
}
}
+
+ // Add a single event to the pending list, possibly combining or collapsing events in the
+ // list.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mCallback")
+ private void addDisplayEvent(int displayId, int event) {
+ if (mPendingEvents == null) {
+ mPendingEvents = new ArrayList<>();
+ }
+ if (!mPendingEvents.isEmpty()) {
+ // Ignore redundant events. Further optimization is possible by merging adjacent
+ // events.
+ Event last = mPendingEvents.get(mPendingEvents.size() - 1);
+ if (last.displayId == displayId && last.event == event) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event
+ + " to " + mUid + "/" + mPid);
+ }
+ return;
+ }
+ }
+ mPendingEvents.add(new Event(displayId, event));
+ }
+
+ /**
+ * @return {@code false} if RemoteException happens; otherwise {@code true} for
+ * success.
+ */
+ boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
+ if ((mInternalEventFlagsMask.get()
+ & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED) == 0) {
+ if (extraLogging(mPackageName)) {
+ Slog.i(TAG, "Not sending topology update: " + topology + " due to mask: "
+ + mInternalEventFlagsMask);
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "notifyTopologyUpdateAsync#notSendingUpdate=" + topology
+ + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
+ }
+ // The client is not interested in this event, so do nothing.
+ return true;
+ }
+ return transmitTopologyUpdate(topology);
+ }
+
+ /**
+ * Transmit a single display topology update. The client is presumed ready. Return true on
+ * success and false if the client died.
+ */
+ private boolean transmitTopologyUpdate(DisplayTopology topology) {
+ // The client is ready to receive the event.
+ try {
+ mCallback.onTopologyChanged(topology);
+ return true;
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that display topology changed, assuming it died.", ex);
+ binderDied();
+ return false;
+ }
+ }
+
+ // Send all pending events. This can safely be called if the process is not ready, but it
+ // would be unusual to do so. The method returns true on success.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ public boolean dispatchPending() {
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty()) {
+ return true;
+ }
+ if (!isReadyLocked()) {
+ return false;
+ }
+ for (int i = 0; i < mPendingEvents.size(); i++) {
+ Event displayEvent = mPendingEvents.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+ if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) {
+ Slog.d(TAG, "Drop pending events for dead process " + mPid);
+ break;
+ }
+ }
+ mPendingEvents.clear();
+ return true;
+ }
+ }
+
+ // Return a string suitable for dumpsys.
+ private String dump() {
+ if (deferDisplayEventsWhenFrozen()) {
+ final String fmt =
+ "mPid=%d mUid=%d mWifiDisplayScanRequested=%s"
+ + " cached=%s frozen=%s pending=%d";
+ synchronized (mCallback) {
+ return formatSimple(fmt,
+ mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen,
+ (mPendingEvents == null) ? 0 : mPendingEvents.size());
+ }
+ } else {
+ final String fmt =
+ "mPid=%d mUid=%d mWifiDisplayScanRequested=%s";
+ return formatSimple(fmt,
+ mPid, mUid, mWifiDisplayScanRequested);
+ }
+ }
}
+ /**
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is false.
+ */
private static final class PendingCallback {
private final CallbackRecord mCallbackRecord;
private final ArrayList<Pair<Integer, Integer>> mDisplayEvents;
@@ -3987,6 +4459,10 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
final class BinderService extends IDisplayManager.Stub {
+ BinderService() {
+ super(PermissionEnforcer.fromContext(getContext()));
+ }
+
/**
* Returns information about the specified logical display.
*
@@ -4047,15 +4523,16 @@ public final class DisplayManagerService extends SystemService {
@Override // Binder call
public void registerCallback(IDisplayManagerCallback callback) {
- registerCallbackWithEventMask(callback, DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ registerCallbackWithEventMask(callback,
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED);
}
@Override // Binder call
@SuppressLint("AndroidFrameworkRequiresPermission") // Permission only required sometimes
public void registerCallbackWithEventMask(IDisplayManagerCallback callback,
- @EventsMask long eventsMask) {
+ @InternalEventFlag long internalEventFlagsMask) {
if (callback == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -4064,7 +4541,9 @@ public final class DisplayManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
if (mFlags.isConnectedDisplayManagementEnabled()) {
- if ((eventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+ if ((internalEventFlagsMask
+ & DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
"Permission required to get signals about connection events.");
}
@@ -4072,7 +4551,7 @@ public final class DisplayManagerService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
- registerCallbackInternal(callback, callingPid, callingUid, eventsMask);
+ registerCallbackInternal(callback, callingPid, callingUid, internalEventFlagsMask);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -4310,19 +4789,10 @@ public final class DisplayManagerService extends SystemService {
@Override // Binder call
public void releaseVirtualDisplay(IVirtualDisplayCallback callback) {
+ final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- releaseVirtualDisplayInternal(callback.asBinder());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
- public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
- final long token = Binder.clearCallingIdentity();
- try {
- setVirtualDisplayStateInternal(callback.asBinder(), isOn);
+ releaseVirtualDisplayInternal(callback.asBinder(), callingUid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -4874,6 +5344,25 @@ public final class DisplayManagerService extends SystemService {
}
return ddc.getDefaultDozeBrightness();
}
+
+ @EnforcePermission(MANAGE_DISPLAYS)
+ @Override // Binder call
+ public DisplayTopology getDisplayTopology() {
+ getDisplayTopology_enforcePermission();
+ if (mDisplayTopologyCoordinator == null) {
+ return null;
+ }
+ return mDisplayTopologyCoordinator.getTopology();
+ }
+
+ @EnforcePermission(MANAGE_DISPLAYS)
+ @Override // Binder call
+ public void setDisplayTopology(DisplayTopology topology) {
+ setDisplayTopology_enforcePermission();
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.setTopology(topology);
+ }
+ }
}
@VisibleForTesting
@@ -4908,6 +5397,23 @@ public final class DisplayManagerService extends SystemService {
}
@Override
+ public void setScreenBrightnessOverrideFromWindowManager(
+ SparseArray<DisplayBrightnessOverrideRequest> brightnessOverrides) {
+ SparseArray<DisplayPowerController> dpcs = new SparseArray<>();
+ synchronized (mSyncRoot) {
+ for (int i = 0; i < mDisplayPowerControllers.size(); i++) {
+ dpcs.put(mDisplayPowerControllers.keyAt(i),
+ mDisplayPowerControllers.valueAt(i));
+ }
+ }
+ for (int i = 0; i < dpcs.size(); ++i) {
+ final int displayId = dpcs.keyAt(i);
+ final DisplayPowerController dpc = dpcs.valueAt(i);
+ dpc.setBrightnessOverrideRequest(brightnessOverrides.get(displayId));
+ }
+ }
+
+ @Override
public boolean requestPowerState(int groupId, DisplayPowerRequest request,
boolean waitForNegativeProximity) {
synchronized (mSyncRoot) {
@@ -4939,10 +5445,9 @@ public final class DisplayManagerService extends SystemService {
}
@Override
- public boolean isProximitySensorAvailable() {
+ public boolean isProximitySensorAvailable(int displayId) {
synchronized (mSyncRoot) {
- return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
- .isProximitySensorAvailable();
+ return mDisplayPowerControllers.get(displayId).isProximitySensorAvailable();
}
}
@@ -5293,6 +5798,31 @@ public final class DisplayManagerService extends SystemService {
}
@Override
+ public int[] getDisplayIdsForGroup(int groupId) {
+ synchronized (mSyncRoot) {
+ return mLogicalDisplayMapper.getDisplayIdsForGroupLocked(groupId);
+ }
+ }
+
+ @Override
+ public SparseArray<int[]> getDisplayIdsByGroupsIds() {
+ synchronized (mSyncRoot) {
+ return mLogicalDisplayMapper.getDisplayIdsByGroupIdLocked();
+ }
+ }
+
+ @Override
+ public IntArray getDisplayIds() {
+ IntArray displayIds = new IntArray();
+ synchronized (mSyncRoot) {
+ mLogicalDisplayMapper.forEachLocked((logicalDisplay -> {
+ displayIds.add(logicalDisplay.getDisplayIdLocked());
+ }), /* includeDisabled= */ false);
+ }
+ return displayIds;
+ }
+
+ @Override
public DisplayManagerInternal.DisplayOffloadSession registerDisplayOffloader(
int displayId, @NonNull DisplayManagerInternal.DisplayOffloader displayOffloader) {
if (!mFlags.isDisplayOffloadEnabled()) {
@@ -5327,6 +5857,26 @@ public final class DisplayManagerService extends SystemService {
public void onPresentation(int displayId, boolean isShown) {
mExternalDisplayPolicy.onPresentation(displayId, isShown);
}
+
+ @Override
+ public void stylusGestureStarted(long eventTime) {
+ if (mFlags.isBlockAutobrightnessChangesOnStylusUsage()) {
+ DisplayPowerController displayPowerController;
+ synchronized (mSyncRoot) {
+ displayPowerController = mDisplayPowerControllers.get(
+ Display.DEFAULT_DISPLAY);
+ }
+ // We assume that the stylus is being used on the default display. This should
+ // be changed to the displayId on which it is being used once we start getting this
+ // information from the input manager service
+ displayPowerController.stylusGestureStarted(eventTime);
+ }
+ }
+
+ @Override
+ public boolean isDisplayReadyForMirroring(int displayId) {
+ return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId);
+ }
}
class DesiredDisplayModeSpecsObserver
@@ -5486,4 +6036,11 @@ public final class DisplayManagerService extends SystemService {
return mExternalDisplayStatsService;
}
}
+
+ /**
+ * Return the value of the pause
+ */
+ private static boolean deferDisplayEventsWhenFrozen() {
+ return com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 04573f4b7514..ab80b14eee2f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -19,9 +19,11 @@ package com.android.server.display;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.brightness.BrightnessEvent.FLAG_EVEN_DIMMER;
import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
import android.animation.Animator;
@@ -40,6 +42,7 @@ import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -166,12 +169,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
+ private static final int MSG_SET_STYLUS_BEING_USED = 19;
+ private static final int MSG_SET_STYLUS_USE_ENDED = 20;
+ private static final int MSG_SET_WINDOW_MANAGER_BRIGHTNESS_OVERRIDE = 21;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
-
// State machine constants for tracking initial brightness ramp skipping when enabled.
private static final int RAMP_STATE_SKIP_NONE = 0;
private static final int RAMP_STATE_SKIP_INITIAL = 1;
@@ -190,6 +193,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
+
+ private static final int STYLUS_USAGE_DEBOUNCE_TIME = 1000;
+ private static final int NANO_SECONDS_TO_MILLI_SECONDS_RATIO = 1_000_000;
+
private static final int[] BRIGHTNESS_RANGE_INDEX = {
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
@@ -497,6 +504,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@GuardedBy("mLock")
private int mPendingOverrideDozeScreenStateLocked;
+ private long mLastStylusUsageEventTime = -1;
+
+ // The time of inactivity after which the stylus can be assumed to be no longer in use.
+ private long mIdleStylusTimeoutMillisConfig = 0;
+
+ // Whether wear bedtime mode is enabled in the settings.
+ private boolean mIsWearBedtimeModeEnabled;
+
/**
* Creates the display power controller.
*/
@@ -517,6 +532,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mSensorManager = sensorManager;
mHandler = new DisplayControllerHandler(handler.getLooper());
mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mIsEnabled = logicalDisplay.isEnabledLocked();
mIsInTransition = logicalDisplay.isInTransitionLocked();
mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
@@ -536,7 +552,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
- if (flags.isBatteryStatsEnabledForAllDisplays()) {
+ if (flags.isBatteryStatsEnabledForAllDisplays()
+ && isDisplaySupportedForBatteryStats(displayDeviceInfo)) {
mBatteryStats = BatteryStatsService.getService();
} else if (mDisplayId == Display.DEFAULT_DISPLAY) {
mBatteryStats = BatteryStatsService.getService();
@@ -551,6 +568,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessTracker = brightnessTracker;
mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
+ mIsWearBedtimeModeEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.Wearable.BEDTIME_MODE, /* def= */ 0) == 1;
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+
final Resources resources = context.getResources();
// DOZE AND DIM SETTINGS
@@ -829,6 +852,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
+ public void setBrightnessOverrideRequest(
+ DisplayManagerInternal.DisplayBrightnessOverrideRequest request) {
+ Message msg = mHandler.obtainMessage(MSG_SET_WINDOW_MANAGER_BRIGHTNESS_OVERRIDE, request);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+ }
+
public void setDisplayOffloadSession(DisplayOffloadSession session) {
if (session == mDisplayOffloadSession) {
return;
@@ -877,6 +906,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mLogicalDisplay.getPowerThrottlingDataIdLocked();
mHandler.postAtTime(() -> {
+ if (mStopped) {
+ // DPC has already stopped, don't execute any more.
+ return;
+ }
+
boolean changed = false;
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -892,6 +926,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mPhysicalDisplayName = displayName;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -1074,6 +1109,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
}
+ if (mFlags.areAutoBrightnessModesEnabled()
+ && mFlags.isAutoBrightnessModeBedtimeWearEnabled()) {
+ BrightnessMappingStrategy bedtimeBrightnessMapper =
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR, mDisplayWhiteBalanceController);
+ if (bedtimeBrightnessMapper != null) {
+ brightnessMappers.put(AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR, bedtimeBrightnessMapper);
+ }
+ }
+
float userLux = BrightnessMappingStrategy.INVALID_LUX;
float userNits = BrightnessMappingStrategy.INVALID_NITS;
if (mAutomaticBrightnessController != null) {
@@ -1143,8 +1188,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits,
- mBrightnessClamperController, mFlags);
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits, mFlags);
mDisplayBrightnessController.setUpAutoBrightness(
mAutomaticBrightnessController, mSensorManager, mDisplayDeviceConfig, mHandler,
defaultModeBrightnessMapper, mIsEnabled, mLeadDisplayId);
@@ -1357,8 +1401,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
- DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
- .updateBrightness(mPowerRequest, state, mDisplayOffloadSession);
+ // Use doze brightness if one of following is true:
+ // 1. The target `state` isDozeState.
+ // 2. Doze power request(POLICY_DOZE) if there's no exception(useNormalBrightnessForDoze).
+ final boolean useDozeBrightness = mFlags.isNormalBrightnessForDozeParameterEnabled(mContext)
+ ? (!mPowerRequest.useNormalBrightnessForDoze && mPowerRequest.policy == POLICY_DOZE)
+ || Display.isDozeState(state) : Display.isDozeState(state);
+ DisplayBrightnessState displayBrightnessState =
+ mDisplayBrightnessController.updateBrightness(
+ mPowerRequest,
+ state,
+ mDisplayOffloadSession,
+ mIsWearBedtimeModeEnabled);
float brightnessState = displayBrightnessState.getBrightness();
float rawBrightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
@@ -1399,16 +1453,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
&& !mAutomaticBrightnessController.isInIdleMode()) {
// Set sendUpdate to false, we're already in updatePowerState() so there's no need
// to trigger it again
- mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
- ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT,
- /* sendUpdate= */ false);
+ if (useDozeBrightness) {
+ mAutomaticBrightnessController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE,
+ /* sendUpdate= */ false);
+ } else if (mFlags.isAutoBrightnessModeBedtimeWearEnabled()
+ && mIsWearBedtimeModeEnabled) {
+ mAutomaticBrightnessController.switchMode(AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ /* sendUpdate= */ false);
+ } else {
+ mAutomaticBrightnessController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* sendUpdate= */ false);
+ }
}
mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
allowAutoBrightnessWhileDozing, mBrightnessReasonTemp.getReason(),
mPowerRequest.policy, mPowerRequest.useNormalBrightnessForDoze,
mDisplayBrightnessController.getLastUserSetScreenBrightness(),
- userSetBrightnessChanged);
+ userSetBrightnessChanged, mIsWearBedtimeModeEnabled);
// If the brightness is already set then it's been overridden by something other than
// the user, or is a temporary adjustment.
@@ -1472,35 +1534,28 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
brightnessState = clampScreenBrightness(brightnessState);
}
- if (mFlags.isNormalBrightnessForDozeParameterEnabled()
- ? !mPowerRequest.useNormalBrightnessForDoze && mPowerRequest.policy == POLICY_DOZE
- : Display.isDozeState(state)) {
- // TODO(b/329676661): Introduce a config property to choose between this brightness
- // strategy and DOZE_DEFAULT
- // On some devices, when auto-brightness is disabled and the device is dozing, we use
- // the current brightness setting scaled by the doze scale factor
- if ((Float.isNaN(brightnessState)
- || displayBrightnessState.getDisplayBrightnessStrategyName()
- .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
- && mFlags.isDisplayOffloadEnabled()
- && mDisplayOffloadSession != null
+ if (useDozeBrightness && (Float.isNaN(brightnessState)
+ || displayBrightnessState.getDisplayBrightnessStrategyName()
+ .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))) {
+ if (mFlags.isDisplayOffloadEnabled() && mDisplayOffloadSession != null
&& (mAutomaticBrightnessController == null
|| !mAutomaticBrightnessStrategy.shouldUseAutoBrightness())) {
+ // TODO(b/329676661): Introduce a config property to choose between this brightness
+ // strategy and DOZE_DEFAULT
+ // On some devices, when auto-brightness is disabled and the device is dozing, we
+ // use the current brightness setting scaled by the doze scale factor
rawBrightnessState = getDozeBrightnessForOffload();
brightnessState = clampScreenBrightness(rawBrightnessState);
- updateScreenBrightnessSetting = false;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
mTempBrightnessEvent.setFlags(
mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
- }
-
- // Use default brightness when dozing unless overridden.
- if (Float.isNaN(brightnessState)
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+ } else if (!mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+ // Use default brightness when dozing unless overridden.
rawBrightnessState = mScreenBrightnessDozeConfig;
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
}
+ updateScreenBrightnessSetting = false;
}
if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
@@ -1553,7 +1608,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
// we broadcast this change through setting.
final float unthrottledBrightnessState = rawBrightnessState;
- DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+ DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(
+ displayBrightnessState, mPowerRequest,
brightnessState, slowChange, /* displayState= */ state);
brightnessState = clampedState.getBrightness();
slowChange = clampedState.isSlowChange();
@@ -1750,6 +1806,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
final float brightnessOnAvailableScale = MathUtils.constrainedMap(0.0f, 1.0f,
clampedState.getMinBrightness(), clampedMax,
brightnessState);
+ final boolean evenDimmerModeOn =
+ mCdsi != null && mCdsi.getReduceBrightColorsActivatedForEvenDimmer();
mTempBrightnessEvent.setPercent(Math.round(
1000.0f * com.android.internal.display.BrightnessUtils.convertLinearToGamma(
brightnessOnAvailableScale) / 10)); // rounded to one dp
@@ -1764,7 +1822,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
- | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+ | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)
+ | (evenDimmerModeOn ? FLAG_EVEN_DIMMER : 0));
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
@@ -1958,7 +2017,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mCachedBrightnessInfo.brightnessMax.value,
mCachedBrightnessInfo.hbmMode.value,
mCachedBrightnessInfo.hbmTransitionPoint.value,
- mCachedBrightnessInfo.brightnessMaxReason.value);
+ mCachedBrightnessInfo.brightnessMaxReason.value,
+ mCachedBrightnessInfo.brightnessReason.value
+ == BrightnessReason.REASON_OVERRIDE);
}
}
@@ -1983,6 +2044,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@BrightnessInfo.BrightnessMaxReason int maxReason =
state != null ? state.getBrightnessMaxReason()
: BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ BrightnessReason brightnessReason = state != null ? state.getBrightnessReason()
+ : new BrightnessReason(BrightnessReason.REASON_UNKNOWN);
final float minBrightness = Math.max(stateMin, Math.min(
mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
final float maxBrightness = Math.min(
@@ -2010,6 +2073,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
maxReason);
+ changed |=
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessReason,
+ brightnessReason.getReason());
return changed;
}
}
@@ -2638,6 +2704,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
+ mCachedBrightnessInfo.hbmTransitionPoint.value);
pw.println(" mCachedBrightnessInfo.brightnessMaxReason ="
+ mCachedBrightnessInfo.brightnessMaxReason.value);
+ pw.println(" mCachedBrightnessInfo.brightnessReason ="
+ + mCachedBrightnessInfo.brightnessReason);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2758,6 +2826,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
+ private static boolean isDisplaySupportedForBatteryStats(DisplayDeviceInfo displayDeviceInfo) {
+ switch (displayDeviceInfo.type) {
+ case Display.TYPE_INTERNAL:
+ case Display.TYPE_EXTERNAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private void dumpBrightnessEvents(PrintWriter pw) {
int size = mBrightnessEventRingBuffer.size();
if (size < 1) {
@@ -2967,6 +3045,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
}
+ public void stylusGestureStarted(long eventTimeNanoSeconds) {
+ long eventTimeMs = eventTimeNanoSeconds / NANO_SECONDS_TO_MILLI_SECONDS_RATIO;
+ if (mLastStylusUsageEventTime == -1
+ || eventTimeMs > mLastStylusUsageEventTime + STYLUS_USAGE_DEBOUNCE_TIME) {
+ synchronized (mLock) {
+ // Add a message to notify the stylus usage has started
+ mHandler.sendEmptyMessageAtTime(MSG_SET_STYLUS_BEING_USED, mClock.uptimeMillis());
+ }
+ mLastStylusUsageEventTime = eventTimeMs;
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -3023,6 +3113,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
updatePowerState();
break;
+ case MSG_SET_WINDOW_MANAGER_BRIGHTNESS_OVERRIDE:
+ if (mDisplayBrightnessController.updateWindowManagerBrightnessOverride(
+ (DisplayManagerInternal.DisplayBrightnessOverrideRequest) msg.obj)) {
+ updatePowerState();
+ }
+ break;
+
case MSG_STOP:
cleanupHandlerThreadAfterStop();
break;
@@ -3083,6 +3180,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
updatePowerState();
}
break;
+ case MSG_SET_STYLUS_BEING_USED:
+ // Remove any MSG_SET_STYLUS_USE_ENDED message from the handler queue and
+ // post a delayed MSG_SET_STYLUS_USE_ENDED message to delay the stylus
+ // usage ended event processing
+ mHandler.removeMessages(MSG_SET_STYLUS_USE_ENDED);
+ Message message = mHandler.obtainMessage(MSG_SET_STYLUS_USE_ENDED);
+ mHandler.sendMessageAtTime(message,
+ mClock.uptimeMillis() + mIdleStylusTimeoutMillisConfig);
+ mDisplayBrightnessController.setStylusBeingUsed(true);
+ break;
+ case MSG_SET_STYLUS_USE_ENDED:
+ mDisplayBrightnessController.setStylusBeingUsed(false);
+ updatePowerState();
+ break;
}
}
}
@@ -3110,6 +3221,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
+ autoBrightnessPresetToString(preset));
setUpAutoBrightness(mContext, mHandler);
sendUpdatePowerState();
+ } else if (uri.equals(
+ Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE))) {
+ mIsWearBedtimeModeEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.Wearable.BEDTIME_MODE, /* def= */ 0) == 1;
+ Slog.i(mTag, "Update for bedtime mode. Enable: " + mIsWearBedtimeModeEnabled);
+ sendUpdatePowerState();
} else {
handleSettingsChange();
}
@@ -3213,7 +3330,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
BrightnessRangeController brightnessModeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController,
DisplayManagerFlags displayManagerFlags) {
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -3304,6 +3420,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
public MutableInt brightnessMaxReason =
new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+ public MutableInt brightnessReason = new MutableInt(BrightnessReason.REASON_UNKNOWN);
public boolean checkAndSetFloat(MutableFloat mf, float f) {
if (mf.value != f) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 631f14755b12..5b78726cc421 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -16,89 +16,145 @@
package com.android.server.display;
-import android.annotation.Nullable;
-import android.util.IndentingPrintWriter;
+import android.hardware.display.DisplayTopology;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
/**
- * This class manages the relative placement (topology) of extended displays. It is responsible for
- * updating and persisting the topology.
+ * Manages the relative placement (topology) of extended displays. Responsible for updating and
+ * persisting the topology.
*/
class DisplayTopologyCoordinator {
+ @GuardedBy("mSyncRoot")
+ private DisplayTopology mTopology;
+
+ /**
+ * Check if extended displays are enabled. If not, a topology is not needed.
+ */
+ private final BooleanSupplier mIsExtendedDisplayEnabled;
+
+ /**
+ * Callback used to send topology updates.
+ * Should be invoked from the corresponding executor.
+ * A copy of the topology should be sent that will not be modified by the system.
+ */
+ private final Consumer<DisplayTopology> mOnTopologyChangedCallback;
+ private final Executor mTopologyChangeExecutor;
+ private final DisplayManagerService.SyncRoot mSyncRoot;
+
+ DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
+ Consumer<DisplayTopology> onTopologyChangedCallback,
+ Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
+ this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
+ topologyChangeExecutor, syncRoot);
+ }
+
+ @VisibleForTesting
+ DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
+ Consumer<DisplayTopology> onTopologyChangedCallback,
+ Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
+ mTopology = injector.getTopology();
+ mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
+ mOnTopologyChangedCallback = onTopologyChangedCallback;
+ mTopologyChangeExecutor = topologyChangeExecutor;
+ mSyncRoot = syncRoot;
+ }
+
/**
- * The topology tree
+ * Add a display to the topology.
+ * @param info The display info
*/
- @Nullable
- private TopologyTreeNode mRoot;
+ void onDisplayAdded(DisplayInfo info) {
+ if (!isDisplayAllowedInTopology(info)) {
+ return;
+ }
+ synchronized (mSyncRoot) {
+ mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info));
+ sendTopologyUpdateLocked();
+ }
+ }
+
+ /**
+ * Remove a display from the topology.
+ * @param displayId The logical display ID
+ */
+ void onDisplayRemoved(int displayId) {
+ synchronized (mSyncRoot) {
+ mTopology.removeDisplay(displayId);
+ sendTopologyUpdateLocked();
+ }
+ }
/**
- * The logical display ID of the primary display that will show certain UI elements.
- * This is not necessarily the same as the default display.
+ * @return A deep copy of the topology.
*/
- private int mPrimaryDisplayId;
+ DisplayTopology getTopology() {
+ synchronized (mSyncRoot) {
+ return mTopology.copy();
+ }
+ }
+
+ void setTopology(DisplayTopology topology) {
+ synchronized (mSyncRoot) {
+ mTopology = topology;
+ mTopology.normalize();
+ sendTopologyUpdateLocked();
+ }
+ }
/**
* Print the object's state and debug information into the given stream.
* @param pw The stream to dump information to.
*/
- public void dump(PrintWriter pw) {
- pw.println("DisplayTopologyCoordinator:");
- pw.println("--------------------");
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.increaseIndent();
-
- ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId);
-
- ipw.println("Topology tree:");
- if (mRoot != null) {
- ipw.increaseIndent();
- mRoot.dump(ipw);
- ipw.decreaseIndent();
+ void dump(PrintWriter pw) {
+ synchronized (mSyncRoot) {
+ mTopology.dump(pw);
}
}
- private static class TopologyTreeNode {
-
- /**
- * The logical display ID
- */
- private int mDisplayId;
-
- private final List<TopologyTreeNode> mChildren = new ArrayList<>();
-
- /**
- * The position of this display relative to its parent.
- */
- private Position mPosition;
-
- /**
- * The distance from the top edge of the parent display to the top edge of this display (in
- * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display
- * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
- * used is density-independent pixels (dp).
- */
- private double mOffset;
-
- /**
- * Print the object's state and debug information into the given stream.
- * @param ipw The stream to dump information to.
- */
- void dump(IndentingPrintWriter ipw) {
- ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition
- + ", offset=" + mOffset + "}");
- ipw.increaseIndent();
- for (TopologyTreeNode child : mChildren) {
- child.dump(ipw);
- }
- ipw.decreaseIndent();
- }
+ /**
+ * @param info The display info
+ * @return The width of the display in dp
+ */
+ private float getWidth(DisplayInfo info) {
+ return info.logicalWidth * (float) DisplayMetrics.DENSITY_DEFAULT
+ / info.logicalDensityDpi;
+ }
+
+ /**
+ * @param info The display info
+ * @return The height of the display in dp
+ */
+ private float getHeight(DisplayInfo info) {
+ return info.logicalHeight * (float) DisplayMetrics.DENSITY_DEFAULT
+ / info.logicalDensityDpi;
+ }
+
+ private boolean isDisplayAllowedInTopology(DisplayInfo info) {
+ return mIsExtendedDisplayEnabled.getAsBoolean()
+ && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
+ }
+
+ @GuardedBy("mSyncRoot")
+ private void sendTopologyUpdateLocked() {
+ DisplayTopology copy = mTopology.copy();
+ mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(copy));
+ }
- private enum Position {
- POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+ @VisibleForTesting
+ static class Injector {
+ DisplayTopology getTopology() {
+ return new DisplayTopology();
}
}
}
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 28a0b28a0167..f34d2cc6e684 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -375,6 +375,54 @@ class ExternalDisplayPolicy {
}
}
+ boolean isDisplayReadyForMirroring(int displayId) {
+ if (!mFlags.isWaitingConfirmationBeforeMirroringEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring CONFIRMED - "
+ + " flag 'waiting for confirmation before mirroring' is disabled");
+ }
+ return true;
+ }
+
+ synchronized (mSyncRoot) {
+ if (!mIsBootCompleted) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "boot is in progress");
+ }
+ return false;
+ }
+
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is null");
+ }
+ return false;
+ }
+
+ if (!isExternalDisplayLocked(logicalDisplay)) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay" + logicalDisplay.getDisplayIdLocked()
+ + " type is " + logicalDisplay.getDisplayInfoLocked().type);
+ }
+ return false;
+ }
+
+ if (!logicalDisplay.isEnabledLocked()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is disabled");
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
@Override
public void notifyThrottling(@NonNull final Temperature temp) {
diff --git a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
index 608fb35cea9a..666bd26db340 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
@@ -19,7 +19,6 @@ package com.android.server.display;
import static android.media.AudioDeviceInfo.TYPE_HDMI;
import static android.media.AudioDeviceInfo.TYPE_HDMI_ARC;
import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
-import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,7 +31,6 @@ import android.media.AudioManager.AudioPlaybackCallback;
import android.media.AudioPlaybackConfiguration;
import android.os.Handler;
import android.os.PowerManager;
-import android.provider.Settings;
import android.util.Slog;
import android.util.SparseIntArray;
import android.view.Display;
@@ -44,6 +42,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.server.display.utils.DebugUtils;
import java.util.List;
+import java.util.function.BooleanSupplier;
/**
@@ -203,8 +202,9 @@ public final class ExternalDisplayStatsService {
}
};
- ExternalDisplayStatsService(Context context, Handler handler) {
- this(new Injector(context, handler));
+ ExternalDisplayStatsService(Context context, Handler handler,
+ BooleanSupplier isExtendedDisplayEnabled) {
+ this(new Injector(context, handler, isExtendedDisplayEnabled));
}
@VisibleForTesting
@@ -599,25 +599,21 @@ public final class ExternalDisplayStatsService {
private final Context mContext;
@NonNull
private final Handler mHandler;
+ private final BooleanSupplier mIsExtendedDisplayEnabled;
@Nullable
private AudioManager mAudioManager;
@Nullable
private PowerManager mPowerManager;
- Injector(@NonNull Context context, @NonNull Handler handler) {
+ Injector(@NonNull Context context, @NonNull Handler handler,
+ BooleanSupplier isExtendedDisplayEnabled) {
mContext = context;
mHandler = handler;
+ mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
}
boolean isExtendedDisplayEnabled() {
- try {
- return 0 != Settings.Global.getInt(
- mContext.getContentResolver(),
- DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0);
- } catch (Throwable e) {
- // Some services might not be initialised yet to be able to call getInt
- return false;
- }
+ return mIsExtendedDisplayEnabled.getAsBoolean();
}
void registerInteractivityReceiver(BroadcastReceiver interactivityReceiver,
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 135cab6d0614..6be0c123d262 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -38,6 +38,7 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.display.DisplayManagerService.Clock;
import com.android.server.display.config.HighBrightnessModeData;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
@@ -119,6 +120,14 @@ class HighBrightnessModeController {
@Nullable
private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+ /**
+ * If {@link DisplayManagerFlags#useNewHdrBrightnessModifier()} is ON, hdr boost is handled by
+ * {@link com.android.server.display.brightness.clamper.HdrBrightnessModifier} and should be
+ * disabled in this class. After flag is cleaned up, this field together with HDR handling
+ * should be cleaned up from this class.
+ */
+ private boolean mHdrBoostDisabled = false;
+
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
@@ -323,6 +332,7 @@ class HighBrightnessModeController {
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
pw.println(" width*height=" + mWidth + "*" + mHeight);
+ pw.println(" mHdrBoostDisabled=" + mHdrBoostDisabled);
if (mHighBrightnessModeMetadata != null) {
pw.println(" mRunningStartTimeMillis="
@@ -373,6 +383,11 @@ class HighBrightnessModeController {
return mHbmData != null && mHighBrightnessModeMetadata != null;
}
+ void disableHdrBoost() {
+ mHdrBoostDisabled = true;
+ unregisterHdrListener();
+ }
+
private long calculateRemainingTime(long currentTime) {
if (!deviceSupportsHbm()) {
return 0;
@@ -583,6 +598,9 @@ class HighBrightnessModeController {
}
private void registerHdrListener(IBinder displayToken) {
+ if (mHdrBoostDisabled) {
+ return;
+ }
if (mRegisteredDisplayToken == displayToken) {
return;
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c14f702e1e7a..798f4d9abeff 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -37,14 +37,15 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.util.DisplayUtils;
import android.util.LongSparseArray;
-import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.Spline;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
import android.view.DisplayShape;
+import android.view.FrameRateCategoryRate;
import android.view.RoundedCorners;
import android.view.SurfaceControl;
@@ -80,10 +81,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private static final String UNIQUE_ID_PREFIX = "local:";
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
- // Min and max strengths for even dimmer feature.
- private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
- private static final float EVEN_DIMMER_MAX_STRENGTH = 90.0f;
- private static final float BRIGHTNESS_MIN = 0.0f;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
@@ -98,7 +95,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private Context mOverlayContext;
private int mEvenDimmerStrength = -1;
+ private boolean mEvenDimmerEnabled = false;
private ColorDisplayService.ColorDisplayServiceInternal mCdsi;
+ private Spline mNitsToEvenDimmerStrength;
// Called with SyncRoot lock held.
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
@@ -247,6 +246,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private int mActiveModeId = INVALID_MODE_ID;
private boolean mDisplayModeSpecsInvalid;
private int mActiveColorMode;
+ private boolean mHasArrSupport;
+ private FrameRateCategoryRate mFrameRateCategoryRate;
+ private float[] mSupportedRefreshRates = new float[0];
private Display.HdrCapabilities mHdrCapabilities;
private boolean mAllmSupported;
private boolean mGameContentTypeSupported;
@@ -278,7 +280,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
mIsFirstDisplay = isFirstDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
- mBacklightAdapter = new BacklightAdapter(displayToken, isFirstDisplay,
+ mBacklightAdapter = mInjector.getBacklightAdapter(displayToken, isFirstDisplay,
mSurfaceControlProxy);
mActiveSfDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
}
@@ -312,6 +314,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
changed |= updateHdrCapabilitiesLocked(dynamicInfo.hdrCapabilities);
changed |= updateAllmSupport(dynamicInfo.autoLowLatencyModeSupported);
changed |= updateGameContentTypeSupport(dynamicInfo.gameContentTypeSupported);
+ changed |= updateHasArrSupportLocked(dynamicInfo.hasArrSupport);
+ changed |= updateFrameRateCategoryRatesLocked(dynamicInfo.frameRateCategoryRate);
+ changed |= updateSupportedRefreshatesLocked(dynamicInfo.supportedRefreshRates);
if (changed) {
mHavePendingChanges = true;
@@ -603,6 +608,37 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return true;
}
+ private boolean updateFrameRateCategoryRatesLocked(
+ FrameRateCategoryRate newFrameRateCategoryRate) {
+ if (Objects.equals(mFrameRateCategoryRate, newFrameRateCategoryRate)) {
+ return false;
+ }
+ mFrameRateCategoryRate = newFrameRateCategoryRate;
+ return true;
+ }
+
+ private boolean updateHasArrSupportLocked(boolean newHasArrSupport) {
+ if (mHasArrSupport == newHasArrSupport) {
+ return false;
+ }
+ mHasArrSupport = newHasArrSupport;
+ return true;
+ }
+
+ private boolean updateSupportedRefreshatesLocked(float[] supportedRefreshRates) {
+ if (!getFeatureFlags().enableGetSupportedRefreshRates()) {
+ return false;
+ }
+ if (supportedRefreshRates == null) {
+ return false;
+ }
+ if (Arrays.equals(mSupportedRefreshRates, supportedRefreshRates)) {
+ return false;
+ }
+ mSupportedRefreshRates = supportedRefreshRates;
+ return true;
+ }
+
private boolean updateAllmSupport(boolean supported) {
if (mAllmSupported == supported) {
return false;
@@ -685,6 +721,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
mInfo.supportedColorModes[i] = mSupportedColorModes.get(i);
}
mInfo.hdrCapabilities = mHdrCapabilities;
+ mInfo.hasArrSupport = mHasArrSupport;
+ mInfo.frameRateCategoryRate = mFrameRateCategoryRate;
+ mInfo.supportedRefreshRates = mSupportedRefreshRates;
mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos;
mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos;
mInfo.state = mState;
@@ -777,9 +816,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
- mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
- mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+ mInfo.brightnessMinimum = getDisplayDeviceConfig().getBrightnessMinimum();
+ mInfo.brightnessMaximum = getDisplayDeviceConfig().getBrightnessMaximum();
mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
+ mInfo.brightnessDim = getDisplayDeviceConfig().getBrightnessDim();
mInfo.hdrSdrRatio = mCurrentHdrSdrRatio;
}
return mInfo;
@@ -1004,26 +1044,36 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
private void applyColorMatrixBasedDimming(float brightnessState) {
- int strength = (int) (MathUtils.constrainedMap(
- // to this range:
- EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH,
- // from this range:
- BRIGHTNESS_MIN, mDisplayDeviceConfig.getEvenDimmerTransitionPoint(),
- // map this (+ rounded up):
- brightnessState) + 0.5);
-
- if (mEvenDimmerStrength < 0 // uninitialised
- || MathUtils.abs(mEvenDimmerStrength - strength) > 1
- || strength <= 1) {
- mEvenDimmerStrength = strength;
- }
- boolean enabled = mEvenDimmerStrength > 0.0f;
-
if (mCdsi == null) {
mCdsi = LocalServices.getService(
ColorDisplayService.ColorDisplayServiceInternal.class);
}
- if (mCdsi != null) {
+ if (mCdsi == null) {
+ return;
+ }
+
+ final float minHardwareNits = backlightToNits(brightnessToBacklight(
+ mDisplayDeviceConfig.getEvenDimmerTransitionPoint()));
+ final float requestedNits =
+ backlightToNits(brightnessToBacklight(brightnessState));
+ mNitsToEvenDimmerStrength =
+ mCdsi.fetchEvenDimmerSpline(minHardwareNits);
+
+ if (mNitsToEvenDimmerStrength == null) {
+ return;
+ }
+
+ // Find required dimming strength, rounded up.
+ int strength = Math.round(mNitsToEvenDimmerStrength
+ .interpolate(requestedNits));
+ boolean enabled = strength > 0.0f;
+ if (mEvenDimmerEnabled != enabled) {
+ Slog.i(TAG, "Setting Extra Dim; strength: " + strength
+ + ", " + (enabled ? "enabled" : "disabled"));
+ }
+ if (mEvenDimmerStrength != strength || mEvenDimmerEnabled != enabled) {
+ mEvenDimmerEnabled = enabled;
+ mEvenDimmerStrength = strength;
mCdsi.applyEvenDimmerColorChanges(enabled, strength);
}
}
@@ -1272,6 +1322,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
pw.println("mActiveColorMode=" + mActiveColorMode);
pw.println("mDefaultModeId=" + mDefaultModeId);
pw.println("mUserPreferredModeId=" + mUserPreferredModeId);
+ pw.println("mHasArrSupport=" + mHasArrSupport);
+ pw.println("mSupportedRefreshRates=" + Arrays.toString(mSupportedRefreshRates));
pw.println("mState=" + Display.stateToString(mState));
pw.println("mCommittedState=" + Display.stateToString(mCommittedState));
pw.println("mBrightnessState=" + mBrightnessState);
@@ -1296,6 +1348,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
pw.println("DisplayDeviceConfig: ");
pw.println("---------------------");
pw.println(mDisplayDeviceConfig);
+ pw.println("mEvenDimmerEnabled=" + mEvenDimmerEnabled);
+ pw.println("mEvenDimmerStrength=" + mEvenDimmerStrength);
+ pw.println("mNitsToEvenDimmerStrength=" + mNitsToEvenDimmerStrength);
}
private int findSfDisplayModeIdLocked(int displayModeId, int modeGroup) {
@@ -1482,6 +1537,12 @@ final class LocalDisplayAdapter extends DisplayAdapter {
long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay, flags);
}
+
+ public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay,
+ SurfaceControlProxy surfaceControlProxy) {
+ return new BacklightAdapter(displayToken, isFirstDisplay, surfaceControlProxy);
+
+ }
}
public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 007e3a8fde2f..1de9c9589fb9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -506,12 +506,16 @@ final class LogicalDisplay {
mBaseDisplayInfo.rotation = Surface.ROTATION_0;
mBaseDisplayInfo.modeId = deviceInfo.modeId;
mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
+ mBaseDisplayInfo.hasArrSupport = deviceInfo.hasArrSupport;
+ mBaseDisplayInfo.frameRateCategoryRate = deviceInfo.frameRateCategoryRate;
+ mBaseDisplayInfo.supportedRefreshRates = Arrays.copyOf(
+ deviceInfo.supportedRefreshRates, deviceInfo.supportedRefreshRates.length);
mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId;
mBaseDisplayInfo.supportedModes = Arrays.copyOf(
deviceInfo.supportedModes, deviceInfo.supportedModes.length);
mBaseDisplayInfo.appsSupportedModes = syntheticModeManager.createAppSupportedModes(
- config, mBaseDisplayInfo.supportedModes
+ config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport
);
mBaseDisplayInfo.colorMode = deviceInfo.colorMode;
mBaseDisplayInfo.supportedColorModes = Arrays.copyOf(
@@ -544,6 +548,7 @@ final class LogicalDisplay {
mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+ mBaseDisplayInfo.brightnessDim = deviceInfo.brightnessDim;
mBaseDisplayInfo.hdrSdrRatio = deviceInfo.hdrSdrRatio;
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index c3f6a5285ae3..c0903a9bafac 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP;
import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
@@ -78,15 +79,18 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// 'adb shell setprop persist.log.tag.LogicalDisplayMapper DEBUG && adb reboot'
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
- public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1;
- public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2;
- public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
- public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
- public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
- public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6;
- public static final int LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED = 7;
- public static final int LOGICAL_DISPLAY_EVENT_CONNECTED = 8;
- public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 9;
+ public static final int LOGICAL_DISPLAY_EVENT_BASE = 0;
+ public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1 << 0;
+ public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 1 << 1;
+ public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 1 << 2;
+ public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3;
+ public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 1 << 4;
+ public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 1 << 5;
+ public static final int LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED = 1 << 6;
+ public static final int LOGICAL_DISPLAY_EVENT_CONNECTED = 1 << 7;
+ public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 1 << 8;
+ public static final int LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED = 1 << 9;
+ public static final int LOGICAL_DISPLAY_EVENT_STATE_CHANGED = 1 << 10;
public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
@@ -344,6 +348,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return displayIds;
}
+ public int[] getDisplayIdsForGroupLocked(int groupId) {
+ DisplayGroup displayGroup = mDisplayGroups.get(groupId);
+ if (displayGroup == null) {
+ return new int[]{};
+ }
+ return displayGroup.getIdsLocked();
+ }
+
+ public SparseArray<int[]> getDisplayIdsByGroupIdLocked() {
+ SparseArray<int[]> displayIdsByGroupIds = new SparseArray<>();
+ for (int i = 0; i < mDisplayGroups.size(); i++) {
+ final int displayGroupId = mDisplayGroups.keyAt(i);
+ displayIdsByGroupIds.put(displayGroupId, getDisplayIdsForGroupLocked(displayGroupId));
+ }
+ return displayIdsByGroupIds;
+ }
+
public void forEachLocked(Consumer<LogicalDisplay> consumer) {
forEachLocked(consumer, /* includeDisabled= */ true);
}
@@ -577,6 +598,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
boolean shouldDeviceBeWoken(DeviceState pendingState, DeviceState currentState,
boolean isInteractive, boolean isBootCompleted) {
if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) {
+ if (currentState.hasProperties(PROPERTY_EMULATED_ONLY)
+ && !pendingState.hasProperties(PROPERTY_EMULATED_ONLY)) {
+ // Do not wake the device, since this transition may occur due to the user pressing
+ // the power button to exit an emulated state.
+ return false;
+ }
+
return pendingState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
&& !currentState.equals(INVALID_DEVICE_STATE)
&& !currentState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
@@ -779,6 +807,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;
final boolean wasPreviouslyEnabled = mDisplaysEnabledCache.get(displayId);
final boolean isCurrentlyEnabled = display.isEnabledLocked();
+ int logicalDisplayEventMask = mLogicalDisplaysToUpdate
+ .get(displayId, LOGICAL_DISPLAY_EVENT_BASE);
// The display is no longer valid and needs to be removed.
if (!display.isValidLocked()) {
@@ -796,20 +826,20 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (mDisplaysEnabledCache.get(displayId)) {
// We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
reloop = true;
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
} else {
mUpdatedLogicalDisplays.delete(displayId);
- mLogicalDisplaysToUpdate.put(displayId,
- LOGICAL_DISPLAY_EVENT_DISCONNECTED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DISCONNECTED;
}
} else {
mUpdatedLogicalDisplays.delete(displayId);
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
}
} else {
// This display never left this class, safe to remove without notification
mLogicalDisplays.removeAt(i);
}
+ mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
continue;
// The display is new.
@@ -817,38 +847,40 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (mFlags.isConnectedDisplayManagementEnabled()) {
// We still need to send LOGICAL_DISPLAY_EVENT_ADDED
reloop = true;
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CONNECTED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CONNECTED;
} else {
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_ADDED;
}
// Underlying displays device has changed to a different one.
} else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_SWAPPED;
// Something about the display device has changed.
} else if (mFlags.isConnectedDisplayManagementEnabled()
&& wasPreviouslyEnabled != isCurrentlyEnabled) {
int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
LOGICAL_DISPLAY_EVENT_REMOVED;
- mLogicalDisplaysToUpdate.put(displayId, event);
+ logicalDisplayEventMask |= event;
} else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
// If only the hdr/sdr ratio changed, then send just the event for that case
if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
- mLogicalDisplaysToUpdate.put(displayId,
- LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED;
} else {
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
}
- // The display is involved in a display layout transition
+ if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()) {
+ logicalDisplayEventMask
+ |= updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo);
+ }
+
+ // The display is involved in a display layout transition
} else if (updateState == UPDATE_STATE_TRANSITION) {
- mLogicalDisplaysToUpdate.put(displayId,
- LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION;
// Display frame rate overrides changed.
} else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
- mLogicalDisplaysToUpdate.put(
- displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED;
// Non-override display values changed.
} else {
@@ -857,10 +889,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// things like display cutouts.
display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
}
}
-
+ mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
}
@@ -897,6 +929,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
}
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
if (mFlags.isConnectedDisplayManagementEnabled()) {
@@ -919,13 +953,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
}
+ @VisibleForTesting
+ int updateAndGetMaskForDisplayPropertyChanges(DisplayInfo newDisplayInfo) {
+ int mask = LOGICAL_DISPLAY_EVENT_BASE;
+ if (mTempDisplayInfo.getRefreshRate() != newDisplayInfo.getRefreshRate()) {
+ mask |= LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
+ }
+
+ if (mTempDisplayInfo.state != newDisplayInfo.state) {
+ mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
+ }
+ return mask;
+ }
/**
* Send the specified message for all relevant displays in the specified display-to-message map.
*/
- private void sendUpdatesForDisplaysLocked(int msg) {
+ private void sendUpdatesForDisplaysLocked(int logicalDisplayEvent) {
for (int i = mLogicalDisplaysToUpdate.size() - 1; i >= 0; --i) {
- final int currMsg = mLogicalDisplaysToUpdate.valueAt(i);
- if (currMsg != msg) {
+ final int logicalDisplayEventMask = mLogicalDisplaysToUpdate.valueAt(i);
+ if ((logicalDisplayEventMask & logicalDisplayEvent) == 0) {
continue;
}
@@ -934,25 +980,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (DEBUG) {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
final String uniqueId = device == null ? "null" : device.getUniqueId();
- Slog.d(TAG, "Sending " + displayEventToString(msg) + " for display=" + id
- + " with device=" + uniqueId);
+ Slog.d(TAG, "Sending " + displayEventToString(logicalDisplayEvent) + " for "
+ + "display=" + id + " with device=" + uniqueId);
}
if (mFlags.isConnectedDisplayManagementEnabled()) {
- if (msg == LOGICAL_DISPLAY_EVENT_ADDED) {
+ if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_ADDED) {
mDisplaysEnabledCache.put(id, true);
- } else if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+ } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
mDisplaysEnabledCache.delete(id);
}
}
- mListener.onLogicalDisplayEventLocked(display, msg);
+ mListener.onLogicalDisplayEventLocked(display, logicalDisplayEvent);
if (mFlags.isConnectedDisplayManagementEnabled()) {
- if (msg == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
+ if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
mLogicalDisplays.delete(id);
}
- } else if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+ } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
// We wait until we sent the EVENT_REMOVED event before actually removing the
// display.
mLogicalDisplays.delete(id);
@@ -1323,6 +1369,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return "connected";
case LOGICAL_DISPLAY_EVENT_DISCONNECTED:
return "disconnected";
+ case LOGICAL_DISPLAY_EVENT_STATE_CHANGED:
+ return "state_changed";
+ case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
+ return "refresh_rate_changed";
}
return null;
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index eb76dcba3b85..382c88327523 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -73,9 +73,13 @@ import java.util.regex.Pattern;
* </pre>
* Supported flags:
* <ul>
- * <li><pre>secure</pre>: creates a secure display</li>
- * <li><pre>own_content_only</pre>: only shows this display's own content</li>
- * <li><pre>should_show_system_decorations</pre>: supports system decorations</li>
+ * <li><code>secure</code>: creates a secure display</li>
+ * <li><code>own_content_only</code>: only shows this display's own content</li>
+ * <li><code>should_show_system_decorations</code>: supports system decorations</li>
+ * <li><code>gravity_top_left</code>: display the overlay at the top left of the screen</li>
+ * <li><code>gravity_top_right</code>: display the overlay at the top right of the screen</li>
+ * <li><code>gravity_bottom_right</code>: display the overlay at the bottom right of the screen</li>
+ * <li><code>gravity_bottom_left</code>: display the overlay at the bottom left of the screen</li>
* </ul>
* </p><p>
* Example:
@@ -113,6 +117,12 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
"should_show_system_decorations";
+ // Gravity flags to decide where the overlay should be shown.
+ private static final String GRAVITY_TOP_LEFT = "gravity_top_left";
+ private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right";
+ private static final String GRAVITY_TOP_RIGHT = "gravity_top_right";
+ private static final String GRAVITY_BOTTOM_LEFT = "gravity_bottom_left";
+
private static final int MIN_WIDTH = 100;
private static final int MIN_HEIGHT = 100;
private static final int MAX_WIDTH = 4096;
@@ -237,8 +247,11 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
String name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_overlay_display_name,
number);
- int gravity = chooseOverlayGravity(number);
OverlayFlags flags = OverlayFlags.parseFlags(flagString);
+ int gravity = flags.mGravity;
+ if (flags.mGravity == Gravity.NO_GRAVITY) {
+ gravity = chooseOverlayGravity(number);
+ }
Slog.i(TAG, "Showing overlay display device #" + number
+ ": name=" + name + ", modes=" + Arrays.toString(modes.toArray())
@@ -266,6 +279,16 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
}
}
+ private static int parseOverlayGravity(String overlayGravity) {
+ return switch (overlayGravity) {
+ case GRAVITY_TOP_LEFT -> Gravity.TOP | Gravity.LEFT;
+ case GRAVITY_TOP_RIGHT -> Gravity.TOP | Gravity.RIGHT;
+ case GRAVITY_BOTTOM_RIGHT -> Gravity.BOTTOM | Gravity.RIGHT;
+ case GRAVITY_BOTTOM_LEFT -> Gravity.BOTTOM | Gravity.LEFT;
+ default -> Gravity.NO_GRAVITY;
+ };
+ }
+
private abstract class OverlayDisplayDevice extends DisplayDevice {
private final String mName;
private final float mRefreshRate;
@@ -605,13 +628,17 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
/** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */
final boolean mShouldShowSystemDecorations;
+ final int mGravity;
+
OverlayFlags(
boolean secure,
boolean ownContentOnly,
- boolean shouldShowSystemDecorations) {
+ boolean shouldShowSystemDecorations,
+ int gravity) {
mSecure = secure;
mOwnContentOnly = ownContentOnly;
mShouldShowSystemDecorations = shouldShowSystemDecorations;
+ mGravity = gravity;
}
static OverlayFlags parseFlags(@Nullable String flagString) {
@@ -619,24 +646,26 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
return new OverlayFlags(
false /* secure */,
false /* ownContentOnly */,
- false /* shouldShowSystemDecorations */);
+ false /* shouldShowSystemDecorations */,
+ Gravity.NO_GRAVITY);
}
boolean secure = false;
boolean ownContentOnly = false;
boolean shouldShowSystemDecorations = false;
+ int gravity = Gravity.NO_GRAVITY;
for (String flag: flagString.split(FLAG_SPLITTER)) {
if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) {
secure = true;
- }
- if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) {
+ } else if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) {
ownContentOnly = true;
- }
- if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
+ } else if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
shouldShowSystemDecorations = true;
+ } else {
+ gravity = parseOverlayGravity(flag);
}
}
- return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations);
+ return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, gravity);
}
@Override
@@ -645,6 +674,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
.append("secure=").append(mSecure)
.append(", ownContentOnly=").append(mOwnContentOnly)
.append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations)
+ .append(", gravity").append(Gravity.toString(mGravity))
.append("}")
.toString();
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 9b02f4b71ebe..836f4ede8f57 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.IBrightnessListener;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -51,17 +52,21 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayShape;
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
@@ -85,6 +90,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private static final AtomicInteger sNextUniqueIndex = new AtomicInteger(0);
private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<>();
+
+ private final int mMaxDevices;
+ private final int mMaxDevicesPerPackage;
+ private final SparseIntArray mNoOfDevicesPerPackage = new SparseIntArray();
+
private final Handler mHandler;
private final SurfaceControlDisplayFactory mSurfaceControlDisplayFactory;
@@ -114,8 +124,31 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
super(syncRoot, context, handler, listener, TAG, featureFlags);
mHandler = handler;
mSurfaceControlDisplayFactory = surfaceControlDisplayFactory;
+
+ mMaxDevices = context.getResources().getInteger(R.integer.config_virtualDisplayLimit);
+ if (mMaxDevices < 1) {
+ throw new IllegalArgumentException("The limit of virtual displays must be >= 1");
+ }
+ mMaxDevicesPerPackage =
+ context.getResources().getInteger(R.integer.config_virtualDisplayLimitPerPackage);
+ if (mMaxDevicesPerPackage < 1) {
+ throw new IllegalArgumentException(
+ "The limit of virtual displays per package must be >= 1");
+ }
}
+ /**
+ * Create a virtual display
+ * @param callback The callback
+ * @param projection The media projection
+ * @param ownerUid The UID of the package creating a display
+ * @param ownerPackageName The name of the package creating a display
+ * @param uniqueId The unique ID of the display device
+ * @param surface The surface
+ * @param flags The flags
+ * @param virtualDisplayConfig The config
+ * @return The display device created
+ */
public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
IMediaProjection projection, int ownerUid, String ownerPackageName, String uniqueId,
Surface surface, int flags, VirtualDisplayConfig virtualDisplayConfig) {
@@ -126,6 +159,22 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
return null;
}
+ if (getFeatureFlags().isVirtualDisplayLimitEnabled()
+ && mVirtualDisplayDevices.size() >= mMaxDevices) {
+ Slog.w(TAG, "Rejecting request to create private virtual display because "
+ + mMaxDevices + " devices already exist.");
+ return null;
+ }
+
+ int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0);
+ if (getFeatureFlags().isVirtualDisplayLimitEnabled()
+ && noOfDevices >= mMaxDevicesPerPackage) {
+ Slog.w(TAG, "Rejecting request to create private virtual display because "
+ + mMaxDevicesPerPackage + " devices already exist for package "
+ + ownerPackageName + ".");
+ return null;
+ }
+
String name = virtualDisplayConfig.getName();
boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
@@ -135,11 +184,17 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
if (projection != null) {
mediaProjectionCallback = new MediaProjectionCallback(appToken);
}
+
+ Callback callbackDelegate = new Callback(
+ callback, virtualDisplayConfig.getBrightnessListener(), mHandler);
VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
- ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler),
+ ownerUid, ownerPackageName, surface, flags, callbackDelegate,
projection, mediaProjectionCallback, uniqueId, virtualDisplayConfig);
mVirtualDisplayDevices.put(appToken, device);
+ if (getFeatureFlags().isVirtualDisplayLimitEnabled()) {
+ mNoOfDevicesPerPackage.put(ownerUid, noOfDevices + 1);
+ }
try {
if (projection != null) {
@@ -150,7 +205,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
appToken.linkToDeath(device, 0);
} catch (RemoteException ex) {
Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
- mVirtualDisplayDevices.remove(appToken);
+ removeVirtualDisplayDeviceLocked(appToken, ownerUid);
device.destroyLocked(false);
return null;
}
@@ -194,8 +249,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
}
- public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
- VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+ /**
+ * Release a virtual display that was previously created
+ * @param appToken The token to identify the display
+ * @param ownerUid The UID of the package, used to keep track of and limit the number of
+ * displays created per package
+ * @return The display device that has been removed
+ */
+ public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken, int ownerUid) {
+ VirtualDisplayDevice device = removeVirtualDisplayDeviceLocked(appToken, ownerUid);
if (device != null) {
Slog.v(TAG, "Release VirtualDisplay " + device.mName);
device.destroyLocked(true);
@@ -207,13 +269,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
return device;
}
- void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
- VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
- if (device != null) {
- device.setDisplayState(isOn);
- }
- }
-
DisplayDevice getDisplayDevice(IBinder appToken) {
return mVirtualDisplayDevices.get(appToken);
}
@@ -235,10 +290,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
: ("," + uid + "," + config.getName() + "," + sNextUniqueIndex.getAndIncrement()));
}
- private void handleBinderDiedLocked(IBinder appToken) {
- mVirtualDisplayDevices.remove(appToken);
- }
-
private void handleMediaProjectionStoppedLocked(IBinder appToken) {
VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
if (device != null) {
@@ -248,6 +299,18 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
}
+ private VirtualDisplayDevice removeVirtualDisplayDeviceLocked(IBinder appToken, int ownerUid) {
+ int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0);
+ if (getFeatureFlags().isVirtualDisplayLimitEnabled()) {
+ if (noOfDevices <= 1) {
+ mNoOfDevicesPerPackage.delete(ownerUid);
+ } else {
+ mNoOfDevicesPerPackage.put(ownerUid, noOfDevices - 1);
+ }
+ }
+ return mVirtualDisplayDevices.remove(appToken);
+ }
+
private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
private static final int PENDING_SURFACE_CHANGE = 0x01;
private static final int PENDING_RESIZE = 0x02;
@@ -266,17 +329,20 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private int mWidth;
private int mHeight;
private int mDensityDpi;
- private float mRequestedRefreshRate;
+ private final float mRequestedRefreshRate;
private Surface mSurface;
private DisplayDeviceInfo mInfo;
private int mDisplayState;
private boolean mStopped;
private int mPendingChanges;
private Display.Mode mMode;
- private boolean mIsDisplayOn;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
- private DisplayCutout mDisplayCutout;
+ private final DisplayCutout mDisplayCutout;
+ private final float mDefaultBrightness;
+ private final float mDimBrightness;
+ private float mCurrentBrightness;
+ private final IBrightnessListener mBrightnessListener;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -293,15 +359,18 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mDensityDpi = virtualDisplayConfig.getDensityDpi();
mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
+ mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness();
+ mDimBrightness = virtualDisplayConfig.getDimBrightness();
+ mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
+ mBrightnessListener = virtualDisplayConfig.getBrightnessListener();
mMode = createMode(mWidth, mHeight, getRefreshRate());
mSurface = surface;
mFlags = flags;
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- mDisplayState = Display.STATE_UNKNOWN;
+ mDisplayState = Display.STATE_ON;
mPendingChanges |= PENDING_SURFACE_CHANGE;
- mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled();
}
@@ -309,7 +378,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
@Override
public void binderDied() {
synchronized (getSyncRoot()) {
- handleBinderDiedLocked(mAppToken);
+ removeVirtualDisplayDeviceLocked(mAppToken, mOwnerUid);
Slog.i(TAG, "Virtual display device released because application token died: "
+ mOwnerPackageName);
destroyLocked(false);
@@ -394,12 +463,21 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
if (state == Display.STATE_OFF) {
mCallback.dispatchDisplayPaused();
} else {
mCallback.dispatchDisplayResumed();
}
}
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && mBrightnessListener != null
+ && BrightnessUtils.isValidBrightnessValue(brightnessState)
+ && brightnessState != mCurrentBrightness) {
+ mCurrentBrightness = brightnessState;
+ mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness);
+ }
return null;
}
@@ -416,12 +494,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
public void setSurfaceLocked(Surface surface) {
if (!mStopped && mSurface != surface) {
- if ((mSurface != null) != (surface != null)) {
+ if (mDisplayState == Display.STATE_ON
+ && ((mSurface == null) != (surface == null))) {
+ mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
}
sendTraversalRequestLocked();
mSurface = surface;
- mInfo = null;
mPendingChanges |= PENDING_SURFACE_CHANGE;
}
}
@@ -439,14 +518,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
}
- void setDisplayState(boolean isOn) {
- if (mIsDisplayOn != isOn) {
- mIsDisplayOn = isOn;
- mInfo = null;
- sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
- }
- }
-
public void stopLocked() {
Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
@@ -567,7 +638,16 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
- mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+ if (mSurface == null) {
+ mInfo.state = Display.STATE_OFF;
+ } else {
+ mInfo.state = mDisplayState;
+ }
+
+ mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
+ mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+ mInfo.brightnessDefault = mDefaultBrightness;
+ mInfo.brightnessDim = mDimBrightness;
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
@@ -588,12 +668,16 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private static final int MSG_ON_DISPLAY_PAUSED = 0;
private static final int MSG_ON_DISPLAY_RESUMED = 1;
private static final int MSG_ON_DISPLAY_STOPPED = 2;
+ private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3;
private final IVirtualDisplayCallback mCallback;
+ private final IBrightnessListener mBrightnessListener;
- public Callback(IVirtualDisplayCallback callback, Handler handler) {
+ Callback(IVirtualDisplayCallback callback, IBrightnessListener brightnessListener,
+ Handler handler) {
super(handler.getLooper());
mCallback = callback;
+ mBrightnessListener = brightnessListener;
}
@Override
@@ -609,6 +693,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
case MSG_ON_DISPLAY_STOPPED:
mCallback.onStopped();
break;
+ case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED:
+ if (mBrightnessListener != null) {
+ mBrightnessListener.onBrightnessChanged((Float) msg.obj);
+ }
+ break;
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
@@ -623,6 +712,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
}
+ public void dispatchRequestedBrightnessChanged(float brightness) {
+ Message msg = obtainMessage(MSG_ON_REQUESTED_BRIGHTNESS_CHANGED, brightness);
+ sendMessage(msg);
+ }
+
public void dispatchDisplayStopped() {
sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index ad57ebfb0600..9e9b899ffa7d 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -44,6 +44,7 @@ public final class BrightnessEvent {
public static final int FLAG_DOZE_SCALE = 0x4;
public static final int FLAG_USER_SET = 0x8;
public static final int FLAG_LOW_POWER_MODE = 0x20;
+ public static final int FLAG_EVEN_DIMMER = 0x40;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -492,6 +493,7 @@ public final class BrightnessEvent {
+ ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
+ ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
+ ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
- + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
+ + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "")
+ + ((mFlags & FLAG_EVEN_DIMMER) != 0 ? "even_dimmer " : "");
}
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index 9a0ee034a8f2..a4804e1887fe 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -50,8 +50,10 @@ public final class BrightnessReason {
public static final int MODIFIER_THROTTLED = 0x8;
public static final int MODIFIER_MIN_LUX = 0x10;
public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20;
+ public static final int MODIFIER_STYLUS_UNDER_USE = 0x40;
public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
- | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND;
+ | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND
+ | MODIFIER_STYLUS_UNDER_USE;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -158,6 +160,9 @@ public final class BrightnessReason {
if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) {
sb.append(" user_min_pref");
}
+ if ((mModifier & MODIFIER_STYLUS_UNDER_USE) != 0) {
+ sb.append(" stylus_under_use");
+ }
int strlen = sb.length();
if (sb.charAt(strlen - 1) == '[') {
sb.setLength(strlen - 2);
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 72a91d59c911..35f6fd047ec6 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -110,6 +110,9 @@ public final class DisplayBrightnessController {
@VisibleForTesting
AutomaticBrightnessController mAutomaticBrightnessController;
+ // True if the stylus is being used
+ private boolean mIsStylusBeingUsed;
+
/**
* The constructor of DisplayBrightnessController.
*/
@@ -147,12 +150,13 @@ public final class DisplayBrightnessController {
public DisplayBrightnessState updateBrightness(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState,
- DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+ DisplayManagerInternal.DisplayOffloadSession displayOffloadSession,
+ boolean isBedtimeModeWearEnabled) {
DisplayBrightnessState state;
synchronized (mLock) {
mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
constructStrategySelectionRequest(displayPowerRequest, targetDisplayState,
- displayOffloadSession));
+ displayOffloadSession, isBedtimeModeWearEnabled));
state = mDisplayBrightnessStrategy
.updateBrightness(constructStrategyExecutionRequest(displayPowerRequest));
}
@@ -176,6 +180,20 @@ public final class DisplayBrightnessController {
}
/**
+ * Updates the brightness override from WindowManager.
+ *
+ * @param request The request to override the brightness
+ * @return whether this request will result in a change of the brightness
+ */
+ public boolean updateWindowManagerBrightnessOverride(
+ DisplayManagerInternal.DisplayBrightnessOverrideRequest request) {
+ synchronized (mLock) {
+ return mDisplayBrightnessStrategySelector.getOverrideBrightnessStrategy()
+ .updateWindowManagerBrightnessOverride(request);
+ }
+ }
+
+ /**
* Sets the brightness to follow
*/
public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
@@ -460,6 +478,8 @@ public final class DisplayBrightnessController {
writer.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault);
writer.println(" mPersistBrightnessNitsForDefaultDisplay="
+ mPersistBrightnessNitsForDefaultDisplay);
+ writer.println(" mIsStylusBeingUsed="
+ + mIsStylusBeingUsed);
synchronized (mLock) {
writer.println(" mPendingScreenBrightness=" + mPendingScreenBrightness);
writer.println(" mCurrentScreenBrightness=" + mCurrentScreenBrightness);
@@ -501,6 +521,18 @@ public final class DisplayBrightnessController {
return true;
}
+ /**
+ * Notifies if the stylus is currently being used or not.
+ */
+ public void setStylusBeingUsed(boolean isEnabled) {
+ mIsStylusBeingUsed = isEnabled;
+ }
+
+ @VisibleForTesting
+ boolean isStylusBeingUsed() {
+ return mIsStylusBeingUsed;
+ }
+
@VisibleForTesting
static class Injector {
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
@@ -612,20 +644,22 @@ public final class DisplayBrightnessController {
private StrategySelectionRequest constructStrategySelectionRequest(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState,
- DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+ DisplayManagerInternal.DisplayOffloadSession displayOffloadSession,
+ boolean isBedtimeModeEnabled) {
boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
float lastUserSetScreenBrightness;
synchronized (mLock) {
lastUserSetScreenBrightness = mLastUserSetScreenBrightness;
}
return new StrategySelectionRequest(displayPowerRequest, targetDisplayState,
- lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession);
+ lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession,
+ mIsStylusBeingUsed, isBedtimeModeEnabled);
}
private StrategyExecutionRequest constructStrategyExecutionRequest(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
float currentScreenBrightness = getCurrentBrightness();
return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness,
- mUserSetScreenBrightnessUpdated);
+ mUserSetScreenBrightnessUpdated, mIsStylusBeingUsed);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 06890e72f068..2c6f37448735 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -109,6 +109,8 @@ public class DisplayBrightnessStrategySelector {
private final int mDisplayId;
+ private final Context mContext;
+
/**
* The constructor of DozeBrightnessStrategy.
*/
@@ -117,6 +119,7 @@ public class DisplayBrightnessStrategySelector {
if (injector == null) {
injector = new Injector();
}
+ mContext = context;
mDisplayManagerFlags = flags;
mDisplayId = displayId;
mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
@@ -180,8 +183,10 @@ public class DisplayBrightnessStrategySelector {
displayBrightnessStrategy = mFollowerBrightnessStrategy;
} else if (displayPowerRequest.boostScreenBrightness) {
displayBrightnessStrategy = mBoostBrightnessStrategy;
- } else if (BrightnessUtils
- .isValidBrightnessValue(displayPowerRequest.screenBrightnessOverride)) {
+ } else if (BrightnessUtils.isValidBrightnessValue(
+ displayPowerRequest.screenBrightnessOverride)
+ || BrightnessUtils.isValidBrightnessValue(
+ mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())) {
displayBrightnessStrategy = mOverrideBrightnessStrategy;
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
@@ -256,6 +261,10 @@ public class DisplayBrightnessStrategySelector {
return mAutoBrightnessFallbackStrategy;
}
+ public OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
+ return mOverrideBrightnessStrategy;
+ }
+
/**
* Dumps the state of this class.
*/
@@ -305,8 +314,10 @@ public class DisplayBrightnessStrategySelector {
strategySelectionRequest.getDisplayPowerRequest().policy,
strategySelectionRequest.getDisplayPowerRequest().useNormalBrightnessForDoze,
strategySelectionRequest.getLastUserSetScreenBrightness(),
- strategySelectionRequest.isUserSetBrightnessChanged());
- return mAutomaticBrightnessStrategy1.isAutoBrightnessValid();
+ strategySelectionRequest.isUserSetBrightnessChanged(),
+ strategySelectionRequest.isWearBedtimeModeEnabled());
+ return !strategySelectionRequest.isStylusBeingUsed()
+ && mAutomaticBrightnessStrategy1.isAutoBrightnessValid();
}
private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
@@ -319,7 +330,8 @@ public class DisplayBrightnessStrategySelector {
strategySelectionRequest.getLastUserSetScreenBrightness(),
strategySelectionRequest.isUserSetBrightnessChanged(),
mAllowAutoBrightnessWhileDozing,
- getAutomaticBrightnessStrategy().shouldUseAutoBrightness());
+ getAutomaticBrightnessStrategy().shouldUseAutoBrightness(),
+ strategySelectionRequest.isWearBedtimeModeEnabled());
}
private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
@@ -339,7 +351,7 @@ public class DisplayBrightnessStrategySelector {
// a user can define a different display state(displayPowerRequest.dozeScreenState) too
// in the request with the Doze policy and user might request an override to force certain
// brightness.
- return (!mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()
+ return (!mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)
|| !displayPowerRequest.useNormalBrightnessForDoze)
&& displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
&& !mAllowAutoBrightnessWhileDozing
diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
index 304640b884ef..7a07c4fc22bf 100644
--- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
@@ -32,11 +32,15 @@ public final class StrategyExecutionRequest {
// Represents if the user set screen brightness was changed or not.
private boolean mUserSetBrightnessChanged;
+ private boolean mIsStylusBeingUsed;
+
public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
- float currentScreenBrightness, boolean userSetBrightnessChanged) {
+ float currentScreenBrightness, boolean userSetBrightnessChanged,
+ boolean isStylusBeingUsed) {
mDisplayPowerRequest = displayPowerRequest;
mCurrentScreenBrightness = currentScreenBrightness;
mUserSetBrightnessChanged = userSetBrightnessChanged;
+ mIsStylusBeingUsed = isStylusBeingUsed;
}
public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -51,6 +55,10 @@ public final class StrategyExecutionRequest {
return mUserSetBrightnessChanged;
}
+ public boolean isStylusBeingUsed() {
+ return mIsStylusBeingUsed;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StrategyExecutionRequest)) {
@@ -59,12 +67,13 @@ public final class StrategyExecutionRequest {
StrategyExecutionRequest other = (StrategyExecutionRequest) obj;
return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
&& mCurrentScreenBrightness == other.getCurrentScreenBrightness()
- && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged();
+ && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged()
+ && mIsStylusBeingUsed == other.isStylusBeingUsed();
}
@Override
public int hashCode() {
return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness,
- mUserSetBrightnessChanged);
+ mUserSetBrightnessChanged, mIsStylusBeingUsed);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
index bfa90e297059..c2bec5ffe052 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -47,13 +47,15 @@ public final class StrategySelectionNotifyRequest {
private final boolean mAllowAutoBrightnessWhileDozingConfig;
// True if the auto brightness is enabled in the settings
private final boolean mIsAutoBrightnessEnabled;
+ // True if wear bedtime mode is enabled in the settings.
+ private final boolean mIsBedtimeModeWearEnabled;
public StrategySelectionNotifyRequest(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState,
DisplayBrightnessStrategy displayBrightnessStrategy,
float lastUserSetScreenBrightness,
boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig,
- boolean isAutoBrightnessEnabled) {
+ boolean isAutoBrightnessEnabled, boolean isBedtimeModeWearEnabled) {
mDisplayPowerRequest = displayPowerRequest;
mTargetDisplayState = targetDisplayState;
mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
@@ -61,6 +63,7 @@ public final class StrategySelectionNotifyRequest {
mUserSetBrightnessChanged = userSetBrightnessChanged;
mAllowAutoBrightnessWhileDozingConfig = allowAutoBrightnessWhileDozingConfig;
mIsAutoBrightnessEnabled = isAutoBrightnessEnabled;
+ mIsBedtimeModeWearEnabled = isBedtimeModeWearEnabled;
}
public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
@@ -81,7 +84,8 @@ public final class StrategySelectionNotifyRequest {
&& mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
&& mAllowAutoBrightnessWhileDozingConfig
== other.isAllowAutoBrightnessWhileDozingConfig()
- && mIsAutoBrightnessEnabled == other.isAutoBrightnessEnabled();
+ && mIsAutoBrightnessEnabled == other.isAutoBrightnessEnabled()
+ && mIsBedtimeModeWearEnabled == other.isBedtimeModeWearEnabled();
}
@Override
@@ -99,6 +103,10 @@ public final class StrategySelectionNotifyRequest {
return mUserSetBrightnessChanged;
}
+ public boolean isBedtimeModeWearEnabled() {
+ return mIsBedtimeModeWearEnabled;
+ }
+
public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
return mDisplayPowerRequest;
}
@@ -126,6 +134,7 @@ public final class StrategySelectionNotifyRequest {
+ " mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness
+ " mUserSetBrightnessChanged=" + mUserSetBrightnessChanged
+ " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig
- + " mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled;
+ + " mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled
+ + " mIsBedtimeModeWearEnabled" + mIsBedtimeModeWearEnabled;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
index aa2f23ef9ec1..2077c9dfb79c 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
@@ -40,15 +40,23 @@ public final class StrategySelectionRequest {
private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+ private boolean mIsStylusBeingUsed;
+
+ private boolean mIsWearBedtimeModeEnabled;
+
public StrategySelectionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState, float lastUserSetScreenBrightness,
boolean userSetBrightnessChanged,
- DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+ DisplayManagerInternal.DisplayOffloadSession displayOffloadSession,
+ boolean isStylusBeingUsed,
+ boolean isWearBedtimeModeEnabled) {
mDisplayPowerRequest = displayPowerRequest;
mTargetDisplayState = targetDisplayState;
mLastUserSetScreenBrightness = lastUserSetScreenBrightness;
mUserSetBrightnessChanged = userSetBrightnessChanged;
mDisplayOffloadSession = displayOffloadSession;
+ mIsStylusBeingUsed = isStylusBeingUsed;
+ mIsWearBedtimeModeEnabled = isWearBedtimeModeEnabled;
}
public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -68,10 +76,18 @@ public final class StrategySelectionRequest {
return mUserSetBrightnessChanged;
}
+ public boolean isWearBedtimeModeEnabled() {
+ return mIsWearBedtimeModeEnabled;
+ }
+
public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSession() {
return mDisplayOffloadSession;
}
+ public boolean isStylusBeingUsed() {
+ return mIsStylusBeingUsed;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StrategySelectionRequest)) {
@@ -82,12 +98,15 @@ public final class StrategySelectionRequest {
&& mTargetDisplayState == other.getTargetDisplayState()
&& mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
&& mUserSetBrightnessChanged == other.isUserSetBrightnessChanged()
- && mDisplayOffloadSession.equals(other.getDisplayOffloadSession());
+ && mDisplayOffloadSession.equals(other.getDisplayOffloadSession())
+ && mIsStylusBeingUsed == other.isStylusBeingUsed()
+ && mIsWearBedtimeModeEnabled == other.isWearBedtimeModeEnabled();
}
@Override
public int hashCode() {
return Objects.hash(mDisplayPowerRequest, mTargetDisplayState,
- mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession);
+ mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession,
+ mIsStylusBeingUsed);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
deleted file mode 100644
index a1fd16476706..000000000000
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.brightness.clamper;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.PowerManager;
-
-import com.android.server.display.DisplayBrightnessState;
-
-import java.io.PrintWriter;
-
-/**
- * Provides brightness range constraints
- */
-abstract class BrightnessClamper<T> {
-
- protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-
- protected boolean mIsActive = false;
-
- @NonNull
- protected final Handler mHandler;
-
- @NonNull
- protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
-
- BrightnessClamper(Handler handler,
- BrightnessClamperController.ClamperChangeListener changeListener) {
- mHandler = handler;
- mChangeListener = changeListener;
- }
-
- float getBrightnessCap() {
- return mBrightnessCap;
- }
-
- float getCustomAnimationRate() {
- return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- }
-
- boolean isActive() {
- return mIsActive;
- }
-
- void dump(PrintWriter writer) {
- writer.println("BrightnessClamper:" + getType());
- writer.println(" mBrightnessCap: " + mBrightnessCap);
- writer.println(" mIsActive: " + mIsActive);
- }
-
- @NonNull
- abstract Type getType();
-
- abstract void onDeviceConfigChanged();
-
- abstract void onDisplayChanged(T displayData);
-
- abstract void stop();
-
- protected enum Type {
- POWER,
- }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index a10094fdfbb8..860be2028eb3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -18,8 +18,6 @@ package com.android.server.display.brightness.clamper;
import static android.view.Display.STATE_ON;
-import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -33,8 +31,8 @@ import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import android.util.Spline;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
@@ -43,7 +41,6 @@ import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
-import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -62,10 +59,10 @@ public class BrightnessClamperController {
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final LightSensorController mLightSensorController;
+ private int mDisplayState = Display.STATE_OFF;
private final ClamperChangeListener mClamperChangeListenerExternal;
private final Executor mExecutor;
- private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
private final List<BrightnessStateModifier> mModifiers;
@@ -77,16 +74,6 @@ public class BrightnessClamperController {
private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
- private final DisplayManagerFlags mDisplayManagerFlags;
- private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-
- private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- @Nullable
- private BrightnessPowerClamper mPowerClamper;
- @Nullable
- private Type mClamperType = null;
-
- private boolean mClamperApplied = false;
private final LightSensorController.LightSensorListener mLightSensorListener =
new LightSensorController.LightSensorListener() {
@@ -96,6 +83,8 @@ public class BrightnessClamperController {
}
};
+ private volatile boolean mStarted = false;
+
public BrightnessClamperController(Handler handler,
ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
@@ -109,32 +98,21 @@ public class BrightnessClamperController {
DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
mHandler = handler;
- mDisplayManagerFlags = flags;
mLightSensorController = injector.getLightSensorController(sensorManager, context,
mLightSensorListener, mHandler);
mClamperChangeListenerExternal = clamperChangeListener;
mExecutor = new HandlerExecutor(handler);
- Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+ Runnable modifiersChangeRunnableInternal = this::recalculateModifiersState;
ClamperChangeListener clamperChangeListenerInternal = () -> {
- if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
- mHandler.post(clamperChangeRunnableInternal);
+ if (mStarted && !mHandler.hasCallbacks(modifiersChangeRunnableInternal)) {
+ mHandler.post(modifiersChangeRunnableInternal);
}
};
- mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
- context, currentBrightness);
- if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
- for (BrightnessClamper clamper: mClampers) {
- if (clamper.getType() == Type.POWER) {
- mPowerClamper = (BrightnessPowerClamper) clamper;
- break;
- }
- }
- }
- mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
- data);
+ mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListenerInternal,
+ data, currentBrightness);
mModifiers.forEach(m -> {
if (m instanceof DisplayDeviceDataListener l) {
@@ -151,7 +129,6 @@ public class BrightnessClamperController {
}
});
mOnPropertiesChangedListener = properties -> {
- mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
mDeviceConfigListeners.forEach(DeviceConfigListener::onDeviceConfigChanged);
};
mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
@@ -163,7 +140,6 @@ public class BrightnessClamperController {
*/
public void onDisplayChanged(DisplayDeviceData data) {
mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
- mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
mDisplayDeviceDataListeners.forEach(l -> l.onDisplayChanged(data));
adjustLightSensorSubscription();
}
@@ -172,58 +148,24 @@ public class BrightnessClamperController {
* Applies clamping
* Called in DisplayControllerHandler
*/
- public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+ public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
+ DisplayManagerInternal.DisplayPowerRequest request,
float brightnessValue, boolean slowChange, int displayState) {
- float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
-
- DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+ mDisplayState = displayState;
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
+ displayBrightnessState);
builder.setIsSlowChange(slowChange);
- builder.setBrightness(cappedBrightness);
- builder.setMaxBrightness(mBrightnessCap);
- builder.setCustomAnimationRate(mCustomAnimationRate);
- builder.setBrightnessMaxReason(getBrightnessMaxReason());
-
- if (mClamperType != null) {
- builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
- if (!mClamperApplied) {
- builder.setIsSlowChange(false);
- }
- mClamperApplied = true;
- } else {
- mClamperApplied = false;
- }
+ builder.setBrightness(brightnessValue);
- if (displayState != STATE_ON) {
- mLightSensorController.stop();
- } else {
- adjustLightSensorSubscription();
- }
+ adjustLightSensorSubscription();
for (int i = 0; i < mModifiers.size(); i++) {
mModifiers.get(i).apply(request, builder);
}
- if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
- if (mPowerClamper != null) {
- mPowerClamper.updateCurrentBrightness(cappedBrightness);
- }
- }
-
return builder.build();
}
- @BrightnessInfo.BrightnessMaxReason
- private int getBrightnessMaxReason() {
- if (mClamperType == null) {
- return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
- } else if (mClamperType == Type.POWER) {
- return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
- } else {
- Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
- return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
- }
- }
-
/**
* Called when the user switches.
*/
@@ -236,13 +178,8 @@ public class BrightnessClamperController {
*/
public void dump(PrintWriter writer) {
writer.println("BrightnessClamperController:");
- writer.println("----------------------------");
- writer.println(" mBrightnessCap: " + mBrightnessCap);
- writer.println(" mClamperType: " + mClamperType);
- writer.println(" mClamperApplied: " + mClamperApplied);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mLightSensorController.dump(ipw);
- mClampers.forEach(clamper -> clamper.dump(ipw));
mModifiers.forEach(modifier -> modifier.dump(ipw));
}
@@ -251,41 +188,20 @@ public class BrightnessClamperController {
* Called in DisplayControllerHandler
*/
public void stop() {
+ mStarted = false;
mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
mOnPropertiesChangedListener);
mLightSensorController.stop();
- mClampers.forEach(BrightnessClamper::stop);
mModifiers.forEach(BrightnessStateModifier::stop);
}
// Called in DisplayControllerHandler
- private void recalculateBrightnessCap() {
- float brightnessCap = PowerManager.BRIGHTNESS_MAX;
- Type clamperType = null;
- float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-
- BrightnessClamper<?> minClamper = mClampers.stream()
- .filter(BrightnessClamper::isActive)
- .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
- clamper2.getBrightnessCap())).orElse(null);
-
- if (minClamper != null) {
- brightnessCap = minClamper.getBrightnessCap();
- clamperType = minClamper.getType();
- customAnimationRate = minClamper.getCustomAnimationRate();
- }
-
+ private void recalculateModifiersState() {
ModifiersAggregatedState newAggregatedState = new ModifiersAggregatedState();
- mStatefulModifiers.forEach((clamper) -> clamper.applyStateChange(newAggregatedState));
-
- if (mBrightnessCap != brightnessCap
- || mClamperType != clamperType
- || mCustomAnimationRate != customAnimationRate
- || needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
- mBrightnessCap = brightnessCap;
- mClamperType = clamperType;
- mCustomAnimationRate = customAnimationRate;
+ mStatefulModifiers.forEach((modifier) -> modifier.applyStateChange(newAggregatedState));
+
+ if (needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
mClamperChangeListenerExternal.onChanged();
}
mModifiersAggregatedState = newAggregatedState;
@@ -304,15 +220,17 @@ public class BrightnessClamperController {
}
private void start() {
- if (!mClampers.isEmpty() || !mDeviceConfigListeners.isEmpty()) {
+ if (!mDeviceConfigListeners.isEmpty()) {
mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
mExecutor, mOnPropertiesChangedListener);
}
adjustLightSensorSubscription();
+ mStarted = true;
}
private void adjustLightSensorSubscription() {
- if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+ if (mDisplayState == STATE_ON && mModifiers.stream()
+ .anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
mLightSensorController.restart();
} else {
mLightSensorController.stop();
@@ -335,33 +253,25 @@ public class BrightnessClamperController {
return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
}
- List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
- DisplayManagerFlags flags, Context context, float currentBrightness) {
- List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
-
- if (flags.isPowerThrottlingClamperEnabled()) {
- // Check if power-throttling config is present.
- PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
- if (configData != null) {
- clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
- data, currentBrightness));
- }
- }
- return clampers;
- }
-
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, ClamperChangeListener listener,
- DisplayDeviceData data) {
+ DisplayDeviceData data, float currentBrightness) {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new BrightnessThermalModifier(handler, listener, data));
if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
modifiers.add(new BrightnessWearBedtimeModeModifier(handler, context,
listener, data));
}
+ if (flags.isPowerThrottlingClamperEnabled()) {
+ // Check if power-throttling config is present.
+ PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
+ if (configData != null) {
+ modifiers.add(new BrightnessPowerModifier(handler, listener,
+ data, currentBrightness));
+ }
+ }
- modifiers.add(new DisplayDimModifier(context));
+ modifiers.add(new DisplayDimModifier(data.mDisplayId, context));
modifiers.add(new BrightnessLowPowerModeModifier());
if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
@@ -392,7 +302,7 @@ public class BrightnessClamperController {
* Config Data for clampers/modifiers
*/
public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData,
- BrightnessPowerClamper.PowerData,
+ BrightnessPowerModifier.PowerData,
BrightnessWearBedtimeModeModifier.WearBedtimeModeData {
@NonNull
private final String mUniqueDisplayId;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index be8fa5a0f0ce..cbeb863971dd 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -16,6 +16,8 @@
package com.android.server.display.brightness.clamper;
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -50,10 +52,12 @@ abstract class BrightnessModifier implements BrightnessStateModifier {
}
if (!mApplied) {
stateBuilder.setIsSlowChange(false);
+ stateBuilder.setCustomAnimationRate(CUSTOM_ANIMATION_RATE_NOT_SET);
}
mApplied = true;
} else if (mApplied) {
stateBuilder.setIsSlowChange(false);
+ stateBuilder.setCustomAnimationRate(CUSTOM_ANIMATION_RATE_NOT_SET);
mApplied = false;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java
index 85e81f989845..146f2f0f0da9 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java
@@ -22,6 +22,8 @@ import static com.android.server.display.brightness.clamper.BrightnessClamperCon
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
@@ -37,6 +39,7 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DeviceConfigParsingUtils;
@@ -48,16 +51,16 @@ import java.util.function.BiFunction;
import java.util.function.Function;
-class BrightnessPowerClamper extends
- BrightnessClamper<BrightnessPowerClamper.PowerData> {
+class BrightnessPowerModifier implements BrightnessStateModifier,
+ BrightnessClamperController.DisplayDeviceDataListener,
+ BrightnessClamperController.StatefulModifier,
+ BrightnessClamperController.DeviceConfigListener {
private static final String TAG = "BrightnessPowerClamper";
@NonNull
- private final Injector mInjector;
- @NonNull
private final DeviceConfigParameterProvider mConfigParameterProvider;
- @Nullable
- private PmicMonitor mPmicMonitor;
+ @NonNull
+ private final PmicMonitor mPmicMonitor;
// data from DeviceConfig, for all displays, for all dataSets
// mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
@NonNull
@@ -71,11 +74,9 @@ class BrightnessPowerClamper extends
@Nullable
private PowerThrottlingData mPowerThrottlingDataActive = null;
@Nullable
- private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
+ private PowerThrottlingConfigData mPowerThrottlingConfigData;
@NonNull
private final ThermalLevelListener mThermalLevelListener;
- @NonNull
- private final PowerChangeListener mPowerChangeListener;
private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
private boolean mCurrentThermalLevelChanged = false;
private float mCurrentAvgPowerConsumed = 0;
@@ -85,7 +86,7 @@ class BrightnessPowerClamper extends
private String mDataId = null;
private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- private float mCustomAnimationRateSecDeviceConfig =
+ private float mCustomAnimationRateDeviceConfig =
DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
try {
@@ -100,28 +101,32 @@ class BrightnessPowerClamper extends
private final Function<List<ThrottlingLevel>, PowerThrottlingData>
mDataSetMapper = PowerThrottlingData::create;
+ protected final Handler mHandler;
+ protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
- BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
+ private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;;
+ private boolean mApplied = false;
+
+
+ BrightnessPowerModifier(Handler handler, ClamperChangeListener listener,
PowerData powerData, float currentBrightness) {
this(new Injector(), handler, listener, powerData, currentBrightness);
}
@VisibleForTesting
- BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
- PowerData powerData, float currentBrightness) {
- super(handler, listener);
- mInjector = injector;
+ BrightnessPowerModifier(@NonNull Injector injector, Handler handler,
+ ClamperChangeListener listener, PowerData powerData, float currentBrightness) {
+ mHandler = handler;
+ mChangeListener = listener;
mCurrentBrightness = currentBrightness;
- mPowerChangeListener = (powerConsumed, thermalStatus) -> {
- recalculatePowerQuotaChange(powerConsumed, thermalStatus);
- };
+
mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
if (mPowerThrottlingConfigData != null) {
- mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+ mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
}
mThermalLevelListener = new ThermalLevelListener(handler);
mPmicMonitor =
- mInjector.getPmicMonitor(mPowerChangeListener,
+ injector.getPmicMonitor(this::recalculatePowerQuotaChange,
mThermalLevelListener.getThermalService(),
mPowerThrottlingConfigData.pollingWindowMaxMillis,
mPowerThrottlingConfigData.pollingWindowMinMillis);
@@ -134,69 +139,91 @@ class BrightnessPowerClamper extends
});
}
- @VisibleForTesting
- PowerChangeListener getPowerChangeListener() {
- return mPowerChangeListener;
+ //region BrightnessStateModifier
+ @Override
+ public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder stateBuilder) {
+ if (stateBuilder.getMaxBrightness() > mBrightnessCap) {
+ stateBuilder.setMaxBrightness(mBrightnessCap);
+ stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap));
+ stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC);
+ stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
+ // set custom animation rate only when modifier is activated.
+ // this will allow auto brightness to apply slow change even when modifier is active
+ if (!mApplied) {
+ stateBuilder.setCustomAnimationRate(mCustomAnimationRateSec);
+ mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ }
+ mApplied = true;
+ } else {
+ mApplied = false;
+ }
+ mCurrentBrightness = stateBuilder.getBrightness();
}
@Override
- @NonNull
- BrightnessClamper.Type getType() {
- return Type.POWER;
+ public void stop() {
+ mPmicMonitor.shutdown();
+ mThermalLevelListener.stop();
+ }
+
+ /**
+ * Dumps the state of BrightnessPowerClamper.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessPowerClamper:");
+ pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+ pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
+ pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel);
+ pw.println(" mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
+ pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+ : mPowerThrottlingDataFromDDC.toString()));
+ pw.println(" mBrightnessCap: " + mBrightnessCap);
+ pw.println(" mApplied: " + mApplied);
+ mThermalLevelListener.dump(pw);
}
@Override
- float getCustomAnimationRate() {
- return mCustomAnimationRateSec;
+ public boolean shouldListenToLightSensor() {
+ return false;
}
@Override
- void onDeviceConfigChanged() {
- mHandler.post(() -> {
- loadOverrideData();
- recalculateActiveData();
- });
+ public void setAmbientLux(float lux) {
+ // noop
}
+ //endregion
+ //region DisplayDeviceDataListener
@Override
- void onDisplayChanged(PowerData data) {
+ public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) {
mHandler.post(() -> {
setDisplayData(data);
recalculateActiveData();
});
}
+ //endregion
+ //region StatefulModifier
@Override
- void stop() {
- if (mPmicMonitor != null) {
- mPmicMonitor.shutdown();
+ public void applyStateChange(
+ BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+ if (aggregatedState.mMaxBrightness > mBrightnessCap) {
+ aggregatedState.mMaxBrightness = mBrightnessCap;
+ aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
}
- if (mThermalLevelListener != null) {
- mThermalLevelListener.stop();
- }
- }
-
- /**
- * Dumps the state of BrightnessPowerClamper.
- */
- public void dump(PrintWriter pw) {
- pw.println("BrightnessPowerClamper:");
- pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
- pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
- pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel);
- pw.println(" mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
- pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
- : mPowerThrottlingDataFromDDC.toString()));
- mThermalLevelListener.dump(pw);
- super.dump(pw);
}
+ //endregion
- /**
- * Updates current brightness, for power calculations.
- */
- public void updateCurrentBrightness(float currentBrightness) {
- mCurrentBrightness = currentBrightness;
+ //region DeviceConfigListener
+ @Override
+ public void onDeviceConfigChanged() {
+ mHandler.post(() -> {
+ loadOverrideData();
+ recalculateActiveData();
+ });
}
+ //endregion
private void recalculateActiveData() {
if (mUniqueDisplayId == null || mDataId == null) {
@@ -206,9 +233,7 @@ class BrightnessPowerClamper extends
.getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
mPowerThrottlingDataFromDDC);
if (mPowerThrottlingDataActive == null) {
- if (mPmicMonitor != null) {
- mPmicMonitor.stop();
- }
+ mPmicMonitor.stop();
}
}
@@ -228,14 +253,9 @@ class BrightnessPowerClamper extends
}
mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
- if (mPowerThrottlingConfigData == null) {
- Slog.d(TAG,
- "Power throttling data is missing for configuration data.");
- }
}
private void recalculateBrightnessCap() {
- boolean isActive = false;
float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
if (mPowerThrottlingDataActive == null) {
@@ -244,7 +264,6 @@ class BrightnessPowerClamper extends
if (powerQuota > 0) {
if (BrightnessUtils.isValidBrightnessValue(mCurrentBrightness)
&& (mCurrentAvgPowerConsumed > powerQuota)) {
- isActive = true;
// calculate new brightness Cap.
// Brightness has a linear relation to power-consumed.
targetBrightnessCap =
@@ -252,11 +271,9 @@ class BrightnessPowerClamper extends
} else if (mCurrentThermalLevelChanged) {
if (mCurrentThermalLevel == Temperature.THROTTLING_NONE) {
// reset pmic and remove the power-throttling cap.
- isActive = true;
targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
mPmicMonitor.stop();
} else {
- isActive = true;
// Since the thermal status has changed, we need to remove power-throttling cap.
// Instead of recalculating and changing brightness again, adding flicker,
// we will wait for the next pmic cycle to re-evaluate this value
@@ -267,7 +284,6 @@ class BrightnessPowerClamper extends
}
}
} else { // Current power consumed is under the quota.
- isActive = true;
targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
}
}
@@ -278,17 +294,16 @@ class BrightnessPowerClamper extends
mPowerThrottlingConfigData.brightnessLowestCapAllowed);
}
- if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
- mIsActive = isActive;
+ if (mBrightnessCap != targetBrightnessCap) {
Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
+ mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
- + " for current screen brightness: " + mCurrentBrightness);
- mBrightnessCap = targetBrightnessCap;
- Slog.i(TAG, "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+ + " for current screen brightness: " + mCurrentBrightness + "\n"
+ + "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+ " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged
+ " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed
- + " mCustomAnimationRateSec:" + mCustomAnimationRateSecDeviceConfig);
- mCustomAnimationRateSec = mCustomAnimationRateSecDeviceConfig;
+ + " mCustomAnimationRateSec:" + mCustomAnimationRateDeviceConfig);
+ mBrightnessCap = targetBrightnessCap;
+ mCustomAnimationRateSec = mCustomAnimationRateDeviceConfig;
mChangeListener.onChanged();
} else {
mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@@ -313,11 +328,7 @@ class BrightnessPowerClamper extends
private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
mHandler.post(() -> {
- if (mCurrentThermalLevel != thermalStatus) {
- mCurrentThermalLevelChanged = true;
- } else {
- mCurrentThermalLevelChanged = false;
- }
+ mCurrentThermalLevelChanged = mCurrentThermalLevel != thermalStatus;
mCurrentThermalLevel = thermalStatus;
mCurrentAvgPowerConsumed = avgPowerConsumed;
recalculateBrightnessCap();
@@ -344,7 +355,7 @@ class BrightnessPowerClamper extends
+ mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
return;
}
- mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+ mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
mThermalLevelListener.start();
}
@@ -402,7 +413,7 @@ class BrightnessPowerClamper extends
@Temperature.ThrottlingStatus int status = temp.getStatus();
if (status >= Temperature.THROTTLING_LIGHT) {
Slog.d(TAG, "Activating pmic monitor due to thermal state:" + status);
- mHandler.post(() -> activatePmicMonitor());
+ mHandler.post(BrightnessPowerModifier.this::activatePmicMonitor);
} else {
if (!mPmicMonitor.isStopped()) {
mHandler.post(() -> deactivatePmicMonitor(status));
@@ -456,6 +467,7 @@ class BrightnessPowerClamper extends
@VisibleForTesting
static class Injector {
+ @NonNull
PmicMonitor getPmicMonitor(PowerChangeListener powerChangeListener,
IThermalService thermalService,
int pollingMaxTimeMillis,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
index ab880bf28743..0237af338b18 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -38,12 +38,12 @@ class DisplayDimModifier extends BrightnessModifier {
// mScreenBrightnessDimConfig.
private final float mScreenBrightnessMinimumDimAmount;
- DisplayDimModifier(Context context) {
+ DisplayDimModifier(int displayId, Context context) {
PowerManager pm = Objects.requireNonNull(context.getSystemService(PowerManager.class));
Resources resources = context.getResources();
mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+ pm.getBrightnessConstraint(displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
mScreenBrightnessMinimumDimAmount = resources.getFloat(
R.dimen.config_screenBrightnessMinimumDimAmountFloat);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
index 355f4fe65279..3b748d719784 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -16,7 +16,7 @@
package com.android.server.display.brightness.clamper;
-import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+import static com.android.server.display.brightness.clamper.BrightnessPowerModifier.PowerChangeListener;
import android.annotation.Nullable;
import android.hardware.power.stats.EnergyConsumer;
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index bf01f2d9c749..94522d197429 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -17,6 +17,7 @@ package com.android.server.display.brightness.strategy;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
@@ -132,10 +133,11 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2
public void setAutoBrightnessState(int targetDisplayState,
boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
boolean useNormalBrightnessForDoze, float lastUserSetScreenBrightness,
- boolean userSetBrightnessChanged) {
+ boolean userSetBrightnessChanged, boolean isBedtimeModeWearEnabled) {
// We are still in the process of updating the power state, so there's no need to trigger
// an update again
- switchMode(targetDisplayState, useNormalBrightnessForDoze, policy, /* sendUpdate= */ false);
+ switchMode(targetDisplayState, useNormalBrightnessForDoze, policy, isBedtimeModeWearEnabled,
+ /* sendUpdate= */ false);
// If the policy is POLICY_DOZE and the display state is not STATE_OFF, auto-brightness
// should only be enabled if the config allows it
@@ -349,7 +351,8 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2
strategySelectionNotifyRequest.getDisplayPowerRequest()
.useNormalBrightnessForDoze,
strategySelectionNotifyRequest.getLastUserSetScreenBrightness(),
- strategySelectionNotifyRequest.isUserSetBrightnessChanged());
+ strategySelectionNotifyRequest.isUserSetBrightnessChanged(),
+ strategySelectionNotifyRequest.isBedtimeModeWearEnabled());
}
mIsConfigured = false;
}
@@ -505,18 +508,33 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2
}
private void switchMode(int state, boolean useNormalBrightnessForDoze, int policy,
- boolean sendUpdate) {
- if (mDisplayManagerFlags.areAutoBrightnessModesEnabled()
- && mAutomaticBrightnessController != null
- && !mAutomaticBrightnessController.isInIdleMode()) {
-
- boolean shouldUseDozeMode =
- mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()
- ? !useNormalBrightnessForDoze && policy == POLICY_DOZE
- : Display.isDozeState(state);
- mAutomaticBrightnessController.switchMode(shouldUseDozeMode
- ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT, sendUpdate);
+ boolean isWearBedtimeModeEnabled, boolean sendUpdate) {
+ if (!mDisplayManagerFlags.areAutoBrightnessModesEnabled()
+ || mAutomaticBrightnessController == null
+ || mAutomaticBrightnessController.isInIdleMode()) {
+ return;
+ }
+
+ final boolean shouldUseBedtimeMode =
+ mDisplayManagerFlags.isAutoBrightnessModeBedtimeWearEnabled()
+ && isWearBedtimeModeEnabled;
+ if (shouldUseBedtimeMode) {
+ mAutomaticBrightnessController.switchMode(AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ sendUpdate);
+ return;
}
+
+ final boolean shouldUseDozeMode =
+ mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)
+ ? (!useNormalBrightnessForDoze && policy == POLICY_DOZE)
+ || Display.isDozeState(state)
+ : Display.isDozeState(state);
+ if (shouldUseDozeMode) {
+ mAutomaticBrightnessController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE, sendUpdate);
+ return;
+ }
+
+ mAutomaticBrightnessController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, sendUpdate);
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
index 5e79565f7c54..6e2e7e704997 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -109,7 +109,7 @@ public class AutomaticBrightnessStrategy2 {
public void setAutoBrightnessState(int targetDisplayState,
boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
boolean useNormalBrightnessForDoze, float lastUserSetScreenBrightness,
- boolean userSetBrightnessChanged) {
+ boolean userSetBrightnessChanged, boolean isBedtimeModeEnabled) {
// If the policy is POLICY_DOZE and the display state is not STATE_OFF, auto-brightness
// should only be enabled if the config allows it
final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
index 7c0c9312888e..b9de34a80bda 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
@@ -37,6 +37,9 @@ public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{
StrategyExecutionRequest strategyExecutionRequest) {
BrightnessReason brightnessReason = new BrightnessReason();
brightnessReason.setReason(BrightnessReason.REASON_MANUAL);
+ if (strategyExecutionRequest.isStylusBeingUsed()) {
+ brightnessReason.setModifier(BrightnessReason.MODIFIER_STYLUS_UNDER_USE);
+ }
return new DisplayBrightnessState.Builder()
.setBrightness(strategyExecutionRequest.getCurrentScreenBrightness())
.setBrightnessReason(brightnessReason)
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 3fc15d120434..649f9dacf8ab 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -16,10 +16,13 @@
package com.android.server.display.brightness.strategy;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.PowerManager;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.StrategyExecutionRequest;
import com.android.server.display.brightness.StrategySelectionNotifyRequest;
@@ -29,6 +32,10 @@ import java.io.PrintWriter;
* Manages the brightness of the display when the system brightness is overridden
*/
public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
+
+ private float mWindowManagerBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ private CharSequence mWindowManagerBrightnessOverrideTag = null;
+
@Override
public DisplayBrightnessState updateBrightness(
StrategyExecutionRequest strategyExecutionRequest) {
@@ -36,9 +43,18 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
// the brightness
DisplayPowerRequest dpr = strategyExecutionRequest.getDisplayPowerRequest();
BrightnessReason reason = new BrightnessReason(BrightnessReason.REASON_OVERRIDE);
- reason.setTag(dpr.screenBrightnessOverrideTag);
+
+ float brightness = dpr.screenBrightnessOverride;
+ if (BrightnessUtils.isValidBrightnessValue(dpr.screenBrightnessOverride)) {
+ brightness = dpr.screenBrightnessOverride;
+ reason.setTag(dpr.screenBrightnessOverrideTag);
+ } else if (BrightnessUtils.isValidBrightnessValue(mWindowManagerBrightnessOverride)) {
+ brightness = mWindowManagerBrightnessOverride;
+ reason.setTag(mWindowManagerBrightnessOverrideTag);
+ }
+
return new DisplayBrightnessState.Builder()
- .setBrightness(dpr.screenBrightnessOverride)
+ .setBrightness(brightness)
.setBrightnessReason(reason)
.setDisplayBrightnessStrategyName(getName())
.build();
@@ -50,7 +66,12 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
}
@Override
- public void dump(PrintWriter writer) {}
+ public void dump(PrintWriter writer) {
+ writer.println("OverrideBrightnessStrategy:");
+ writer.println(" mWindowManagerBrightnessOverride=" + mWindowManagerBrightnessOverride);
+ writer.println(" mWindowManagerBrightnessOverrideTag="
+ + mWindowManagerBrightnessOverrideTag);
+ }
@Override
public void strategySelectionPostProcessor(
@@ -58,6 +79,37 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
// DO NOTHING
}
+ /**
+ * Updates the brightness override from WindowManager.
+ *
+ * @param request The request to override the brightness
+ * @return whether this request will result in a change of the brightness
+ */
+ public boolean updateWindowManagerBrightnessOverride(
+ DisplayManagerInternal.DisplayBrightnessOverrideRequest request) {
+ float newBrightness = request == null
+ ? PowerManager.BRIGHTNESS_INVALID_FLOAT : request.brightness;
+ mWindowManagerBrightnessOverrideTag = request == null ? null : request.tag;
+
+ if (floatEquals(newBrightness, mWindowManagerBrightnessOverride)) {
+ return false;
+ }
+
+ mWindowManagerBrightnessOverride = newBrightness;
+ return true;
+ }
+
+ /**
+ * Returns the current brightness override from WindowManager.
+ */
+ public float getWindowManagerBrightnessOverride() {
+ return mWindowManagerBrightnessOverride;
+ }
+
+ private boolean floatEquals(float f1, float f2) {
+ return f1 == f2 || (Float.isNaN(f1) && Float.isNaN(f2));
+ }
+
@Override
public int getReason() {
return BrightnessReason.REASON_OVERRIDE;
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 6e3e5786a9b1..7892639fc8ed 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -70,6 +70,7 @@ import android.provider.Settings.System;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseIntArray;
+import android.util.Spline;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
@@ -115,6 +116,8 @@ public final class ColorDisplayService extends SystemService {
Matrix.setIdentityM(MATRIX_IDENTITY, 0);
}
+ private static final int EVEN_DIMMER_MAX_PERCENT_ALLOWED = 100;
+
private static final int MSG_USER_CHANGED = 0;
private static final int MSG_SET_UP = 1;
private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2;
@@ -194,6 +197,9 @@ public final class ColorDisplayService extends SystemService {
private final boolean mVisibleBackgroundUsersEnabled;
private final UserManagerService mUserManager;
+ private Spline mEvenDimmerSpline;
+ private boolean mEvenDimmerActivated;
+
public ColorDisplayService(Context context) {
super(context);
mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -1626,6 +1632,16 @@ public final class ColorDisplayService extends SystemService {
}
/**
+ * Gets the adjusted nits, given a strength and nits.
+ * @param strength of reduce bright colors
+ * @param nits target nits
+ * @return the actual nits that would be output, given input nits and rbc strength.
+ */
+ public float getAdjustedNitsForStrength(float nits, int strength) {
+ return mReduceBrightColorsTintController.getAdjustedNitsForStrength(nits, strength);
+ }
+
+ /**
* Sets the listener and returns whether reduce bright colors is currently enabled.
*/
public boolean setReduceBrightColorsListener(ReduceBrightColorsListener listener) {
@@ -1645,6 +1661,14 @@ public final class ColorDisplayService extends SystemService {
}
/**
+ *
+ * @return whether reduce bright colors is on, due to even dimmer being activated
+ */
+ public boolean getReduceBrightColorsActivatedForEvenDimmer() {
+ return mEvenDimmerActivated;
+ }
+
+ /**
* Gets the computed brightness, in nits, when the reduce bright colors feature is applied
* at the current strength.
*
@@ -1668,10 +1692,42 @@ public final class ColorDisplayService extends SystemService {
* Applies tint changes for even dimmer feature.
*/
public void applyEvenDimmerColorChanges(boolean enabled, int strength) {
+ mEvenDimmerActivated = enabled;
mReduceBrightColorsTintController.setActivated(enabled);
mReduceBrightColorsTintController.setMatrix(strength);
mHandler.sendEmptyMessage(MSG_APPLY_REDUCE_BRIGHT_COLORS);
}
+
+ /**
+ * Get spline to map between requested nits, and required even dimmer strength.
+ * @return nits to strength spline
+ */
+ public Spline fetchEvenDimmerSpline(float nits) {
+ if (mEvenDimmerSpline == null) {
+ mEvenDimmerSpline = createNitsToStrengthSpline(nits);
+ }
+ return mEvenDimmerSpline;
+ }
+
+ /**
+ * Creates a spline mapping requested nits values, for each resulting strength value
+ * (in percent) for even dimmer.
+ * Returns null if coefficients are not initialised.
+ * @return spline from nits to strength
+ */
+ private Spline createNitsToStrengthSpline(float nits) {
+ final float[] requestedNits = new float[EVEN_DIMMER_MAX_PERCENT_ALLOWED + 1];
+ final float[] resultingStrength = new float[EVEN_DIMMER_MAX_PERCENT_ALLOWED + 1];
+ for (int i = 0; i <= EVEN_DIMMER_MAX_PERCENT_ALLOWED; i++) {
+ resultingStrength[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] = i;
+ requestedNits[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] =
+ getAdjustedNitsForStrength(nits, i);
+ if (requestedNits[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] == 0) {
+ return null;
+ }
+ }
+ return new Spline.LinearSpline(requestedNits, resultingStrength);
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
index f529c4c65a9a..4f6fbd003d9a 100644
--- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
+++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
@@ -123,6 +123,14 @@ public class ReduceBrightColorsTintController extends TintController {
return computeComponentValue(mStrength) * nits;
}
+ /**
+ * Returns the effective brightness (in nits), which has been adjusted to account for the effect
+ * of the bright color reduction.
+ */
+ public float getAdjustedNitsForStrength(float nits, int strength) {
+ return computeComponentValue(strength) * nits;
+ }
+
private float computeComponentValue(int strengthLevel) {
final float percentageStrength = strengthLevel / 100f;
final float squaredPercentageStrength = percentageStrength * percentageStrength;
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index e0bdda511df3..458438c3f0b0 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -16,6 +16,7 @@
package com.android.server.display.config;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
@@ -247,6 +248,9 @@ public class DisplayBrightnessMappingConfig {
case AUTO_BRIGHTNESS_MODE_DOZE -> {
return AutoBrightnessModeName.doze.getRawName();
}
+ case AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR -> {
+ return AutoBrightnessModeName.bedtime_wear.getRawName();
+ }
default -> throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
}
}
diff --git a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 7e2e10a7639f..9a590a293ea3 100644
--- a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -34,72 +34,76 @@ public class EvenDimmerBrightnessData {
/**
* Brightness value at which even dimmer methods are used.
*/
- public final float mTransitionPoint;
+ public final float transitionPoint;
/**
* Nits array, maps to mBacklight
*/
- public final float[] mNits;
+ public final float[] nits;
/**
* Backlight array, maps to mBrightness and mNits
*/
- public final float[] mBacklight;
+ public final float[] backlight;
/**
* Brightness array, maps to mBacklight
*/
- public final float[] mBrightness;
+ public final float[] brightness;
+
/**
* Spline, mapping between backlight and nits
*/
- public final Spline mBacklightToNits;
+ public final Spline backlightToNits;
+
/**
* Spline, mapping between nits and backlight
*/
- public final Spline mNitsToBacklight;
+ public final Spline nitsToBacklight;
+
/**
* Spline, mapping between brightness and backlight
*/
- public final Spline mBrightnessToBacklight;
+ public final Spline brightnessToBacklight;
+
/**
* Spline, mapping between backlight and brightness
*/
- public final Spline mBacklightToBrightness;
+ public final Spline backlightToBrightness;
/**
* Spline, mapping the minimum nits for each lux condition.
*/
- public final Spline mMinLuxToNits;
+ public final Spline minLuxToNits;
@VisibleForTesting
public EvenDimmerBrightnessData(float transitionPoint, float[] nits,
float[] backlight, float[] brightness, Spline backlightToNits,
Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness,
Spline minLuxToNits) {
- mTransitionPoint = transitionPoint;
- mNits = nits;
- mBacklight = backlight;
- mBrightness = brightness;
- mBacklightToNits = backlightToNits;
- mNitsToBacklight = nitsToBacklight;
- mBrightnessToBacklight = brightnessToBacklight;
- mBacklightToBrightness = backlightToBrightness;
- mMinLuxToNits = minLuxToNits;
+ this.transitionPoint = transitionPoint;
+ this.nits = nits;
+ this.backlight = backlight;
+ this.brightness = brightness;
+ this.backlightToNits = backlightToNits;
+ this.nitsToBacklight = nitsToBacklight;
+ this.brightnessToBacklight = brightnessToBacklight;
+ this.backlightToBrightness = backlightToBrightness;
+ this.minLuxToNits = minLuxToNits;
}
@Override
public String toString() {
return "EvenDimmerBrightnessData {"
- + "mTransitionPoint: " + mTransitionPoint
- + ", mNits: " + Arrays.toString(mNits)
- + ", mBacklight: " + Arrays.toString(mBacklight)
- + ", mBrightness: " + Arrays.toString(mBrightness)
- + ", mBacklightToNits: " + mBacklightToNits
- + ", mNitsToBacklight: " + mNitsToBacklight
- + ", mBrightnessToBacklight: " + mBrightnessToBacklight
- + ", mBacklightToBrightness: " + mBacklightToBrightness
- + ", mMinLuxToNits: " + mMinLuxToNits
+ + "transitionPoint: " + transitionPoint
+ + ", nits: " + Arrays.toString(nits)
+ + ", backlight: " + Arrays.toString(backlight)
+ + ", brightness: " + Arrays.toString(brightness)
+ + ", backlightToNits: " + backlightToNits
+ + ", nitsToBacklight: " + nitsToBacklight
+ + ", brightnessToBacklight: " + brightnessToBacklight
+ + ", backlightToBrightness: " + backlightToBrightness
+ + ", minLuxToNits: " + minLuxToNits
+ "} ";
}
@@ -122,7 +126,6 @@ public class EvenDimmerBrightnessData {
if (map == null) {
return null;
}
- String interpolation = map.getInterpolation();
List<BrightnessPoint> brightnessPoints = map.getBrightnessPoint();
if (brightnessPoints.isEmpty()) {
@@ -169,22 +172,11 @@ public class EvenDimmerBrightnessData {
++i;
}
- // Explicitly choose linear interpolation.
- if ("linear".equals(interpolation)) {
- return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
- new Spline.LinearSpline(backlight, nits),
- new Spline.LinearSpline(nits, backlight),
- new Spline.LinearSpline(brightness, backlight),
- new Spline.LinearSpline(backlight, brightness),
- new Spline.LinearSpline(minLux, minNits)
- );
- }
-
return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
- Spline.createSpline(backlight, nits),
- Spline.createSpline(nits, backlight),
- Spline.createSpline(brightness, backlight),
- Spline.createSpline(backlight, brightness),
+ new Spline.LinearSpline(backlight, nits),
+ new Spline.LinearSpline(nits, backlight),
+ new Spline.LinearSpline(brightness, backlight),
+ new Spline.LinearSpline(backlight, brightness),
Spline.createSpline(minLux, minNits)
);
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index df66893a2f35..585fc44fa452 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -16,6 +16,7 @@
package com.android.server.display.feature;
+import android.content.Context;
import android.os.Build;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -199,16 +200,72 @@ public class DisplayManagerFlags {
Flags.FLAG_IDLE_SCREEN_CONFIG_IN_SUBSCRIBING_LIGHT_SENSOR,
Flags::idleScreenConfigInSubscribingLightSensor);
+ private final FlagState mVirtualDisplayLimit =
+ new FlagState(
+ Flags.FLAG_VIRTUAL_DISPLAY_LIMIT,
+ Flags::virtualDisplayLimit);
+
private final FlagState mNormalBrightnessForDozeParameter = new FlagState(
Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
Flags::normalBrightnessForDozeParameter
);
+ private final FlagState mBlockAutobrightnessChangesOnStylusUsage = new FlagState(
+ Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
+ Flags::blockAutobrightnessChangesOnStylusUsage
+ );
+ private final FlagState mIsUserRefreshRateForExternalDisplayEnabled = new FlagState(
+ Flags.FLAG_ENABLE_USER_REFRESH_RATE_FOR_EXTERNAL_DISPLAY,
+ Flags::enableUserRefreshRateForExternalDisplay
+ );
+
+ private final FlagState mEnableWaitingConfirmationBeforeMirroring = new FlagState(
+ Flags.FLAG_ENABLE_WAITING_CONFIRMATION_BEFORE_MIRRORING,
+ Flags::enableWaitingConfirmationBeforeMirroring
+ );
+
+ private final FlagState mEnableApplyDisplayChangedDuringDisplayAdded = new FlagState(
+ Flags.FLAG_ENABLE_APPLY_DISPLAY_CHANGED_DURING_DISPLAY_ADDED,
+ Flags::enableApplyDisplayChangedDuringDisplayAdded
+ );
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
Flags::enableBatteryStatsForAllDisplays
);
+ private final FlagState mHasArrSupport = new FlagState(
+ Flags.FLAG_ENABLE_HAS_ARR_SUPPORT,
+ Flags::enableHasArrSupport
+ );
+
+ private final FlagState mAutoBrightnessModeBedtimeWearFlagState = new FlagState(
+ Flags.FLAG_AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Flags::autoBrightnessModeBedtimeWear
+ );
+
+ private final FlagState mGetSupportedRefreshRatesFlagState = new FlagState(
+ Flags.FLAG_ENABLE_GET_SUPPORTED_REFRESH_RATES,
+ Flags::enableGetSupportedRefreshRates
+ );
+
+ private final FlagState mEnablePluginManagerFlagState = new FlagState(
+ Flags.FLAG_ENABLE_PLUGIN_MANAGER,
+ Flags::enablePluginManager
+ );
+ private final FlagState mDisplayListenerPerformanceImprovementsFlagState = new FlagState(
+ Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS,
+ Flags::displayListenerPerformanceImprovements
+ );
+ private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState(
+ Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
+ Flags::enableDisplayContentModeManagement
+ );
+
+ private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
+ Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS,
+ Flags::subscribeGranularDisplayEvents
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -412,11 +469,16 @@ public class DisplayManagerFlags {
return mNewHdrBrightnessModifier.isEnabled();
}
+ public boolean isVirtualDisplayLimitEnabled() {
+ return mVirtualDisplayLimit.isEnabled();
+ }
+
/**
* @return Whether the useDozeBrightness parameter should be used
*/
- public boolean isNormalBrightnessForDozeParameterEnabled() {
- return mNormalBrightnessForDozeParameter.isEnabled();
+ public boolean isNormalBrightnessForDozeParameterEnabled(Context context) {
+ return mNormalBrightnessForDozeParameter.isEnabled() && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowNormalBrightnessForDozePolicy);
}
/**
@@ -428,6 +490,14 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if mirroring won't be enabled until boot completes and the user enables
+ * the display.
+ */
+ public boolean isWaitingConfirmationBeforeMirroringEnabled() {
+ return mEnableWaitingConfirmationBeforeMirroring.isEnabled();
+ }
+
+ /**
* @return {@code true} if battery stats is enabled for all displays, not just the primary
* display.
*/
@@ -436,6 +506,72 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if need to apply display changes during display added event.
+ */
+ public boolean isApplyDisplayChangedDuringDisplayAddedEnabled() {
+ return mEnableApplyDisplayChangedDuringDisplayAdded.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if autobrightness is to be blocked when stylus is being used
+ */
+ public boolean isBlockAutobrightnessChangesOnStylusUsage() {
+ return mBlockAutobrightnessChangesOnStylusUsage.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if need to use user refresh rate settings for
+ * external displays.
+ */
+ public boolean isUserRefreshRateForExternalDisplayEnabled() {
+ return mIsUserRefreshRateForExternalDisplayEnabled.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if hasArrSupport API is enabled.
+ */
+ public boolean hasArrSupportFlag() {
+ return mHasArrSupport.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if bedtime mode specific auto-brightness curve should be loaded and be
+ * applied when bedtime mode is enabled.
+ */
+ public boolean isAutoBrightnessModeBedtimeWearEnabled() {
+ return mAutoBrightnessModeBedtimeWearFlagState.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if supported refresh rate api is enabled.
+ */
+ public boolean enableGetSupportedRefreshRates() {
+ return mGetSupportedRefreshRatesFlagState.isEnabled();
+ }
+
+ public boolean isPluginManagerEnabled() {
+ return mEnablePluginManagerFlagState.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if the flag for display listener performance improvements is enabled
+ */
+ public boolean isDisplayListenerPerformanceImprovementsEnabled() {
+ return mDisplayListenerPerformanceImprovementsFlagState.isEnabled();
+ }
+
+ public boolean isDisplayContentModeManagementEnabled() {
+ return mEnableDisplayContentModeManagementFlagState.isEnabled();
+ }
+
+ /**
+ * @return {@code true} if the flag for subscribing to granular display events is enabled
+ */
+ public boolean isSubscribeGranularDisplayEventsEnabled() {
+ return mSubscribeGranularDisplayEvents.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -476,9 +612,21 @@ public class DisplayManagerFlags {
pw.println(" " + mOffloadDozeOverrideHoldsWakelock);
pw.println(" " + mOffloadSessionCancelBlockScreenOn);
pw.println(" " + mNewHdrBrightnessModifier);
+ pw.println(" " + mVirtualDisplayLimit);
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
+ pw.println(" " + mEnableWaitingConfirmationBeforeMirroring);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
+ pw.println(" " + mEnableApplyDisplayChangedDuringDisplayAdded);
+ pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
+ pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
+ pw.println(" " + mHasArrSupport);
+ pw.println(" " + mAutoBrightnessModeBedtimeWearFlagState);
+ pw.println(" " + mGetSupportedRefreshRatesFlagState);
+ pw.println(" " + mEnablePluginManagerFlagState);
+ pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
+ pw.println(" " + mSubscribeGranularDisplayEvents);
+ pw.println(" " + mEnableDisplayContentModeManagementFlagState);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e3ebe5bcd9ed..123b7dfbf843 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -4,6 +4,15 @@ container: "system"
# Important: Flags must be accessed through DisplayManagerFlags.
flag {
+ name: "is_always_on_available_api"
+ namespace: "display_manager"
+ description: "Allows querying of AOD availability"
+ bug: "324046664"
+ is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
name: "enable_port_in_display_layout"
namespace: "display_manager"
description: "Allows refering to displays by port in display layout"
@@ -259,6 +268,7 @@ flag {
description: "Feature flag for an API to get the highest defined HDR/SDR ratio for a display."
bug: "335181559"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -348,6 +358,14 @@ flag {
}
flag {
+ name: "virtual_display_limit"
+ namespace: "display_manager"
+ description: "Limit the number of virtual displays that can be created."
+ bug: "261791612"
+ is_fixed_read_only: true
+}
+
+flag {
name: "idle_screen_config_in_subscribing_light_sensor"
namespace: "display_manager"
description: "Account for Idle screen refresh rate configs while subscribing to light sensor"
@@ -359,9 +377,121 @@ flag {
}
flag {
+ name: "enable_waiting_confirmation_before_mirroring"
+ namespace: "display_manager"
+ description: "Allow ContentRecorder checking whether user confirmed mirroring after boot"
+ bug: "361698995"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_battery_stats_for_all_displays"
namespace: "display_manager"
description: "Flag to enable battery stats for all displays."
bug: "366112793"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "enable_apply_display_changed_during_display_added"
+ namespace: "display_manager"
+ description: "Apply display changes after display added"
+ bug: "368131655"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "block_autobrightness_changes_on_stylus_usage"
+ namespace: "display_manager"
+ description: "Block the usage of ALS to control the display brightness when stylus is being used"
+ bug: "352411468"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_user_refresh_rate_for_external_display"
+ namespace: "display_manager"
+ description: "Apply refresh rate from user preferred display mode to external displays"
+ bug: "370657357"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_has_arr_support"
+ namespace: "core_graphics"
+ description: "Flag for an API to get whether display supports ARR or not"
+ bug: "361433651"
+ is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
+ name: "auto_brightness_mode_bedtime_wear"
+ namespace: "wear_frameworks"
+ description: "Feature flag for loading and applying auto-brightness curve while wear bedtime mode enabled."
+ bug: "350617205"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_get_suggested_frame_rate"
+ namespace: "core_graphics"
+ description: "Flag for an API to get suggested frame rates"
+ bug: "361433796"
+ is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
+ name: "display_listener_performance_improvements"
+ namespace: "display_manager"
+ description: "Feature flag for an API to let the apps subscribe to a specific property change of the Display."
+ bug: "372700957"
+ is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
+ name: "enable_get_supported_refresh_rates"
+ namespace: "core_graphics"
+ description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
+ bug: "365163968"
+ is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
+ name: "enable_plugin_manager"
+ namespace: "display_manager"
+ description: "Flag to enable DisplayManager plugins"
+ bug: "354059797"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_display_content_mode_management"
+ namespace: "lse_desktop_experience"
+ description: "Enable switching the content mode of connected displays between mirroring and extened. Also change the default content mode to extended mode."
+ bug: "378385869"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "subscribe_granular_display_events"
+ namespace: "display_manager"
+ description: "Enable subscription to granular display change events."
+ bug: "379250634"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 18e0d6ee5ea3..8423e1911764 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -156,6 +156,8 @@ public class DisplayModeDirector {
// a map from display id to display device config
private SparseArray<DisplayDeviceConfig> mDisplayDeviceConfigByDisplay = new SparseArray<>();
+ private SparseBooleanArray mHasArrSupport;
+
private BrightnessObserver mBrightnessObserver;
private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
@@ -194,6 +196,8 @@ public class DisplayModeDirector {
private final boolean mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled;
+ private final boolean mHasArrSupportFlagEnabled;
+
private final DisplayManagerFlags mDisplayManagerFlags;
private final DisplayDeviceConfigProvider mDisplayDeviceConfigProvider;
@@ -218,6 +222,7 @@ public class DisplayModeDirector {
.isDisplaysRefreshRatesSynchronizationEnabled();
mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags
.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled();
+ mHasArrSupportFlagEnabled = displayManagerFlags.hasArrSupportFlag();
mDisplayManagerFlags = displayManagerFlags;
mDisplayDeviceConfigProvider = displayDeviceConfigProvider;
mContext = context;
@@ -228,6 +233,7 @@ public class DisplayModeDirector {
mSupportedModesByDisplay = new SparseArray<>();
mAppSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
+ mHasArrSupport = new SparseBooleanArray();
mAppRequestObserver = new AppRequestObserver(displayManagerFlags);
mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
@@ -452,7 +458,13 @@ public class DisplayModeDirector {
return mAppRequestObserver;
}
+ // TODO(b/372019752) Rename all the occurrences of the VRR with ARR.
private boolean isVrrSupportedLocked(int displayId) {
+ if (mHasArrSupportFlagEnabled) {
+ Boolean hasArrSupport = mHasArrSupport.get(displayId);
+ return hasArrSupport != null && hasArrSupport;
+ }
+ // TODO(b/371041638) Remove config.isVrrSupportEnabled once hasArrSupport is rolled out
DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.get(displayId);
return config != null && config.isVrrSupportEnabled();
}
@@ -1194,6 +1206,13 @@ public class DisplayModeDirector {
@GuardedBy("mLock")
private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
float defaultRefreshRate, int displayId) {
+ if (mDisplayObserver.isExternalDisplayLocked(displayId)) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "skip updateRefreshRateSettingLocked for external display "
+ + displayId);
+ }
+ return;
+ }
// TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
// used to predict if we're going to be doing frequent refresh rate switching, and if
// so, enable the brightness observer. The logic here is more complicated and fragile
@@ -1243,6 +1262,8 @@ public class DisplayModeDirector {
}
private void removeRefreshRateSetting(int displayId) {
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+ null);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
null);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
@@ -1458,11 +1479,12 @@ public class DisplayModeDirector {
public void onDisplayAdded(int displayId) {
updateDisplayDeviceConfig(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
+ registerExternalDisplay(displayInfo);
updateDisplayModes(displayId, displayInfo);
+ updateHasArrSupport(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
updateDisplaysPeakRefreshRateAndResolution(displayInfo);
- addDisplaysSynchronizedPeakRefreshRate(displayInfo);
}
@Override
@@ -1473,22 +1495,48 @@ public class DisplayModeDirector {
mDefaultModeByDisplay.remove(displayId);
mDisplayDeviceConfigByDisplay.remove(displayId);
mSettingsObserver.removeRefreshRateSetting(displayId);
+ mHasArrSupport.delete(displayId);
}
updateLayoutLimitedFrameRate(displayId, null);
removeUserSettingDisplayPreferredSize(displayId);
removeDisplaysPeakRefreshRateAndResolution(displayId);
- removeDisplaysSynchronizedPeakRefreshRate(displayId);
+ unregisterExternalDisplay(displayId);
}
@Override
public void onDisplayChanged(int displayId) {
updateDisplayDeviceConfig(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
+ updateHasArrSupport(displayId, displayInfo);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
}
+ private void registerExternalDisplay(DisplayInfo displayInfo) {
+ if (displayInfo == null || displayInfo.type != Display.TYPE_EXTERNAL) {
+ return;
+ }
+ synchronized (mLock) {
+ mExternalDisplaysConnected.add(displayInfo.displayId);
+ if (mExternalDisplaysConnected.size() == 1) {
+ addDisplaysSynchronizedPeakRefreshRate();
+ }
+ }
+ }
+
+ private void unregisterExternalDisplay(int displayId) {
+ synchronized (mLock) {
+ if (!isExternalDisplayLocked(displayId)) {
+ return;
+ }
+ mExternalDisplaysConnected.remove(displayId);
+ if (mExternalDisplaysConnected.isEmpty()) {
+ removeDisplaysSynchronizedPeakRefreshRate();
+ }
+ }
+ }
+
boolean isExternalDisplayLocked(int displayId) {
return mExternalDisplaysConnected.contains(displayId);
}
@@ -1534,10 +1582,24 @@ public class DisplayModeDirector {
return;
}
- mVotesStorage.updateVote(info.displayId,
- Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
- Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
- /* height */ preferredMode.getPhysicalHeight()));
+ if (info.type == Display.TYPE_EXTERNAL
+ && mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()
+ && !isRefreshRateSynchronizationEnabled()) {
+ mVotesStorage.updateVote(info.displayId,
+ Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ /* minWidth */ preferredMode.getPhysicalWidth(),
+ /* minHeight */ preferredMode.getPhysicalHeight(),
+ /* width */ preferredMode.getPhysicalWidth(),
+ /* height */ preferredMode.getPhysicalHeight(),
+ /* minRefreshRate */ preferredMode.getRefreshRate(),
+ /* maxRefreshRate */ preferredMode.getRefreshRate()));
+ } else {
+ mVotesStorage.updateVote(info.displayId,
+ Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+ Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
+ /* height */ preferredMode.getPhysicalHeight()));
+ }
}
@Nullable
@@ -1584,17 +1646,10 @@ public class DisplayModeDirector {
* Sets 60Hz target refresh rate as the vote with
* {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority.
*/
- private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) {
- if (info == null || info.type != Display.TYPE_EXTERNAL
- || !isRefreshRateSynchronizationEnabled()) {
+ private void addDisplaysSynchronizedPeakRefreshRate() {
+ if (!isRefreshRateSynchronizationEnabled()) {
return;
}
- synchronized (mLock) {
- mExternalDisplaysConnected.add(info.displayId);
- if (mExternalDisplaysConnected.size() != 1) {
- return;
- }
- }
// set minRefreshRate as the max refresh rate.
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE,
Vote.forPhysicalRefreshRates(
@@ -1610,19 +1665,10 @@ public class DisplayModeDirector {
+ SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
}
- private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) {
+ private void removeDisplaysSynchronizedPeakRefreshRate() {
if (!isRefreshRateSynchronizationEnabled()) {
return;
}
- synchronized (mLock) {
- if (!isExternalDisplayLocked(displayId)) {
- return;
- }
- mExternalDisplaysConnected.remove(displayId);
- if (!mExternalDisplaysConnected.isEmpty()) {
- return;
- }
- }
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, null);
}
@@ -1660,6 +1706,16 @@ public class DisplayModeDirector {
}
}
}
+
+ private void updateHasArrSupport(int displayId, @Nullable DisplayInfo info) {
+ if (info == null) {
+ return;
+ }
+ synchronized (mLock) {
+ mHasArrSupport.put(displayId, info.hasArrSupport);
+ }
+ }
+
}
/**
@@ -2021,8 +2077,8 @@ public class DisplayModeDirector {
mDeviceConfigDisplaySettings.startListening();
mInjector.registerDisplayListener(this, mHandler,
- DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+ DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
}
private void setLoggingEnabled(boolean loggingEnabled) {
@@ -2822,8 +2878,8 @@ public class DisplayModeDirector {
}
mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
mInjector.registerDisplayListener(this, mHandler,
- DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+ DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
}
/**
@@ -3052,6 +3108,9 @@ public class DisplayModeDirector {
void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
Handler handler, long flags);
+ void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+ Handler handler, long flags, long privateFlags);
+
Display getDisplay(int displayId);
Display[] getDisplays();
@@ -3119,6 +3178,12 @@ public class DisplayModeDirector {
}
@Override
+ public void registerDisplayListener(DisplayManager.DisplayListener listener,
+ Handler handler, long flags, long privateFlags) {
+ getDisplayManager().registerDisplayListener(listener, handler, flags, privateFlags);
+ }
+
+ @Override
public Display getDisplay(int displayId) {
return getDisplayManager().getDisplay(displayId);
}
diff --git a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
index a83b9395dfc0..71b34679882d 100644
--- a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
+++ b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
@@ -37,17 +37,22 @@ public class SyntheticModeManager {
SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE;
private final boolean mSynthetic60HzModesEnabled;
+ private final boolean mHasArrSupportEnabled;
public SyntheticModeManager(DisplayManagerFlags flags) {
mSynthetic60HzModesEnabled = flags.isSynthetic60HzModesEnabled();
+ mHasArrSupportEnabled = flags.hasArrSupportFlag();
}
/**
* creates display supportedModes array, exposed to applications
*/
public Display.Mode[] createAppSupportedModes(DisplayDeviceConfig config,
- Display.Mode[] modes) {
- if (!config.isVrrSupportEnabled() || !mSynthetic60HzModesEnabled) {
+ Display.Mode[] modes, boolean hasArrSupport) {
+ // TODO(b/361433651) Remove config.isVrrSupportEnabled once hasArrSupport is rolled out
+ boolean isArrSupported =
+ mHasArrSupportEnabled ? hasArrSupport : config.isVrrSupportEnabled();
+ if (!isArrSupported || !mSynthetic60HzModesEnabled) {
return modes;
}
List<Display.Mode> appSupportedModes = new ArrayList<>();
diff --git a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
index 15f19cca99db..4a4c8da1a335 100644
--- a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
+++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -28,12 +29,15 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
/**
* SystemRequestObserver responsible for handling system requests to filter allowable display
* modes
*/
class SystemRequestObserver {
+ private static final String TAG = "SystemRequestObserver";
+
private final VotesStorage mVotesStorage;
private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@@ -43,6 +47,7 @@ class SystemRequestObserver {
}
@Override
public void binderDied(@NonNull IBinder who) {
+ Slog.d(TAG, "binder died: " + who);
removeSystemRequestedVotes(who);
who.unlinkToDeath(mDeathRecipient, 0);
}
@@ -83,9 +88,11 @@ class SystemRequestObserver {
updateStorageLocked(displayId);
}
if (needLinkToDeath) {
+ Slog.d(TAG, "binder linking to death: " + token);
token.linkToDeath(mDeathRecipient, 0);
}
} catch (RemoteException re) {
+ Slog.d(TAG, "linking to death failed: " + token, re);
removeSystemRequestedVotes(token);
}
}
@@ -94,14 +101,19 @@ class SystemRequestObserver {
boolean needToUnlink = false;
synchronized (mLock) {
SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token);
- if (modesByDisplay != null) {
+ if (modesByDisplay != null && modesByDisplay.size() > 0) {
modesByDisplay.remove(displayId);
needToUnlink = modesByDisplay.size() == 0;
updateStorageLocked(displayId);
}
}
if (needToUnlink) {
- token.unlinkToDeath(mDeathRecipient, 0);
+ try {
+ Slog.d(TAG, "binder unlinking to death: " + token);
+ token.unlinkToDeath(mDeathRecipient, 0);
+ } catch (NoSuchElementException e) {
+ Slog.d(TAG, "unlinking to death failed: " + token, e);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 459f9a6e8f13..f5abb0561ce7 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -44,7 +44,7 @@ interface Vote {
// It votes [minRefreshRate, Float.POSITIVE_INFINITY]
int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
- // User setting preferred display resolution.
+ // User setting preferred display resolution, for external displays also includes refresh rate.
int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4;
// APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
diff --git a/services/core/java/com/android/server/display/plugin/Plugin.java b/services/core/java/com/android/server/display/plugin/Plugin.java
new file mode 100644
index 000000000000..1bef4641b9ad
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/Plugin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface that OEMs should implement in order to inject custom code to system process.
+ * Communication between OEM Plugin and Framework is implemented via {@link PluginStorage}.
+ * OEM Plugin pushes values to PluginStorage, that are picked up by
+ * {@link PluginManager.PluginChangeListener}, implemented on Framework side.
+ * Avoid calling heavy operations in constructor - it will be called during boot and will
+ * negatively impact boot time. Use onBootComplete and separate thread for long running operations.
+ */
+@KeepForApi
+public interface Plugin {
+
+ /**
+ * Called when device boot completed
+ */
+ void onBootCompleted();
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ */
+ void dump(PrintWriter pw);
+}
+
diff --git a/services/core/java/com/android/server/display/plugin/PluginEventStorage.java b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java
new file mode 100644
index 000000000000..c58ba556bcb6
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.util.RingBuffer;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+class PluginEventStorage {
+ private static final long TIME_FRAME_LENGTH = 60_000;
+ private static final long MIN_EVENT_DELAY = 500;
+ private static final int MAX_TIME_FRAMES = 10;
+ // not thread safe
+ private static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
+ "MM-dd HH:mm:ss.SSS", Locale.US);
+
+ RingBuffer<TimeFrame> mEvents = new RingBuffer<>(
+ TimeFrame::new, TimeFrame[]::new, MAX_TIME_FRAMES);
+
+ private final Map<PluginType<?>, Long> mEventTimes = new HashMap<>();
+ private long mTimeFrameStart = 0;
+ private final Map<PluginType<?>, EventCounter> mCounters = new HashMap<>();
+
+ <T> void onValueUpdated(PluginType<T> type) {
+ long eventTime = System.currentTimeMillis();
+ if (eventTime - TIME_FRAME_LENGTH > mTimeFrameStart) { // event is in next TimeFrame
+ closeCurrentTimeFrame();
+ mTimeFrameStart = eventTime;
+ }
+ updateCurrentTimeFrame(type, eventTime);
+ }
+
+ private void closeCurrentTimeFrame() {
+ if (!mCounters.isEmpty()) {
+ mEvents.append(new TimeFrame(
+ mTimeFrameStart, mTimeFrameStart + TIME_FRAME_LENGTH, mCounters));
+ mCounters.clear();
+ }
+ }
+
+ private <T> void updateCurrentTimeFrame(PluginType<T> type, long eventTime) {
+ EventCounter counter = mCounters.get(type);
+ long previousTimestamp = mEventTimes.getOrDefault(type, 0L);
+ if (counter == null) {
+ counter = new EventCounter();
+ mCounters.put(type, counter);
+ }
+ counter.increase(eventTime, previousTimestamp);
+ mEventTimes.put(type, eventTime);
+ }
+
+ List<TimeFrame> getTimeFrames() {
+ List<TimeFrame> timeFrames = new ArrayList<>(Arrays.stream(mEvents.toArray()).toList());
+ timeFrames.add(new TimeFrame(
+ mTimeFrameStart, System.currentTimeMillis(), mCounters));
+ return timeFrames;
+ }
+
+ static class TimeFrame {
+ private final long mStart;
+ private final long mEnd;
+ private final Map<PluginType<?>, EventCounter> mCounters;
+
+ private TimeFrame() {
+ this(0, 0, Map.of());
+ }
+
+ private TimeFrame(long start, long end, Map<PluginType<?>, EventCounter> counters) {
+ mStart = start;
+ mEnd = end;
+ mCounters = new HashMap<>(counters);
+ }
+
+ @SuppressWarnings("JavaUtilDate")
+ void dump(PrintWriter pw) {
+ pw.append("TimeFrame:[")
+ .append(sDateFormat.format(new Date(mStart)))
+ .append(" - ")
+ .append(sDateFormat.format(new Date(mEnd)))
+ .println("]:");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ if (mCounters.isEmpty()) {
+ ipw.println("NO EVENTS");
+ } else {
+ for (Map.Entry<PluginType<?>, EventCounter> entry: mCounters.entrySet()) {
+ ipw.append(entry.getKey().mName).append(" -> {");
+ entry.getValue().dump(ipw);
+ ipw.println("}");
+ }
+ }
+ }
+ }
+
+ private static class EventCounter {
+ private int mEventCounter = 0;
+ private int mFastEventCounter = 0;
+
+ private void increase(long timestamp, long previousTimestamp) {
+ mEventCounter++;
+ if (timestamp - previousTimestamp < MIN_EVENT_DELAY) {
+ mFastEventCounter++;
+ }
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.append("Count:").append(String.valueOf(mEventCounter))
+ .append("; Fast:").append(String.valueOf(mFastEventCounter));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/plugin/PluginManager.java b/services/core/java/com/android/server/display/plugin/PluginManager.java
new file mode 100644
index 000000000000..d4099975cafa
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginManager.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SystemServerClassLoaderFactory;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import dalvik.system.PathClassLoader;
+
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Responsible for loading Plugins. Plugins and PluginSupplier are loaded from
+ * standalone system jar.
+ * Plugin manager will look for PROVIDER_IMPL_CLASS in configured jar.
+ * After device booted, PluginManager will delegate this call to each Plugin
+ */
+public class PluginManager {
+ private static final String PROVIDER_IMPL_CLASS =
+ "com.android.server.display.plugin.PluginsProviderImpl";
+ private static final String TAG = "PluginManager";
+
+ private final DisplayManagerFlags mFlags;
+ private final PluginStorage mPluginStorage;
+ private final List<Plugin> mPlugins;
+
+ public PluginManager(Context context, DisplayManagerFlags flags) {
+ this(context, flags, new Injector());
+ }
+
+ @VisibleForTesting
+ PluginManager(Context context, DisplayManagerFlags flags, Injector injector) {
+ mFlags = flags;
+ mPluginStorage = injector.getPluginStorage();
+ if (mFlags.isPluginManagerEnabled()) {
+ mPlugins = Collections.unmodifiableList(injector.loadPlugins(context, mPluginStorage));
+ Slog.d(TAG, "loaded Plugins:" + mPlugins);
+ } else {
+ mPlugins = List.of();
+ Slog.d(TAG, "PluginManager disabled");
+ }
+ }
+
+ /**
+ * Forwards boot completed event to Plugins
+ */
+ public void onBootCompleted() {
+ mPlugins.forEach(Plugin::onBootCompleted);
+ }
+
+ /**
+ * Adds change listener for particular plugin type
+ */
+ public <T> void subscribe(PluginType<T> type, PluginChangeListener<T> listener) {
+ mPluginStorage.addListener(type, listener);
+ }
+
+ /**
+ * Removes change listener
+ */
+ public <T> void unsubscribe(PluginType<T> type, PluginChangeListener<T> listener) {
+ mPluginStorage.removeListener(type, listener);
+ }
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("PluginManager:");
+ mPluginStorage.dump(pw);
+ for (Plugin plugin : mPlugins) {
+ plugin.dump(pw);
+ }
+ }
+
+ /**
+ * Listens for changes in PluginStorage for a particular type
+ * @param <T> plugin value type
+ */
+ public interface PluginChangeListener<T> {
+ /**
+ * Called when Plugin value changed
+ */
+ void onChanged(@Nullable T value);
+ }
+
+ static class Injector {
+ PluginStorage getPluginStorage() {
+ return new PluginStorage();
+ }
+
+ List<Plugin> loadPlugins(Context context, PluginStorage storage) {
+ String providerJarPath = context
+ .getString(com.android.internal.R.string.config_pluginsProviderJarPath);
+ Slog.d(TAG, "loading plugins from:" + providerJarPath);
+ if (TextUtils.isEmpty(providerJarPath)) {
+ return List.of();
+ }
+ try {
+ PathClassLoader pathClassLoader =
+ SystemServerClassLoaderFactory.getOrCreateClassLoader(
+ providerJarPath, getClass().getClassLoader(), false);
+ @SuppressWarnings("PrivateApi")
+ Class<? extends PluginsProvider> cp = pathClassLoader.loadClass(PROVIDER_IMPL_CLASS)
+ .asSubclass(PluginsProvider.class);
+ PluginsProvider provider = cp.getDeclaredConstructor().newInstance();
+ return provider.getPlugins(context, storage);
+ } catch (ClassNotFoundException e) {
+ Slog.e(TAG, "loading failed: " + PROVIDER_IMPL_CLASS + " is not found in"
+ + providerJarPath, e);
+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException
+ | NoSuchMethodException e) {
+ Slog.e(TAG, "Class instantiation failed", e);
+ }
+ return List.of();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java
new file mode 100644
index 000000000000..dd3415fb614d
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores values pushed by Plugins and forwards them to corresponding listener.
+ */
+public class PluginStorage {
+ private static final String TAG = "PluginStorage";
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<PluginType<?>, Object> mValues = new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>();
+ @GuardedBy("mLock")
+ private final PluginEventStorage mPluginEventStorage = new PluginEventStorage();
+
+ /**
+ * Updates value in storage and forwards it to corresponding listeners.
+ * Should be called by OEM Plugin implementation in order to provide communicate with Framework
+ */
+ @KeepForApi
+ public <T> void updateValue(PluginType<T> type, @Nullable T value) {
+ Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value);
+ Set<PluginManager.PluginChangeListener<T>> localListeners;
+ synchronized (mLock) {
+ mValues.put(type, value);
+ mPluginEventStorage.onValueUpdated(type);
+ ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
+ localListeners = new LinkedHashSet<>(container.mListeners);
+ }
+ Slog.d(TAG, "updateValue, notifying listeners=" + localListeners);
+ localListeners.forEach(l -> l.onChanged(value));
+ }
+
+ /**
+ * Adds listener for PluginType. If storage already has value for this type, listener will
+ * be notified immediately.
+ */
+ <T> void addListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) {
+ T value = null;
+ synchronized (mLock) {
+ ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
+ if (container.mListeners.add(listener)) {
+ value = getValueForTypeLocked(type);
+ }
+ }
+ if (value != null) {
+ listener.onChanged(value);
+ }
+ }
+
+ /**
+ * Removes listener
+ */
+ <T> void removeListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) {
+ synchronized (mLock) {
+ ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
+ container.mListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ */
+ void dump(PrintWriter pw) {
+ Map<PluginType<?>, Object> localValues;
+ @SuppressWarnings("rawtypes")
+ Map<PluginType, Set> localListeners = new HashMap<>();
+ List<PluginEventStorage.TimeFrame> timeFrames;
+ synchronized (mLock) {
+ timeFrames = mPluginEventStorage.getTimeFrames();
+ localValues = new HashMap<>(mValues);
+ mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners));
+ }
+ pw.println("PluginStorage:");
+ pw.println("values=" + localValues);
+ pw.println("listeners=" + localListeners);
+ pw.println("PluginEventStorage:");
+ for (PluginEventStorage.TimeFrame timeFrame: timeFrames) {
+ timeFrame.dump(pw);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @SuppressWarnings("unchecked")
+ private <T> T getValueForTypeLocked(PluginType<T> type) {
+ Object value = mValues.get(type);
+ if (value == null) {
+ return null;
+ } else if (type.mType == value.getClass()) {
+ return (T) value;
+ } else {
+ Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName()
+ + ", expected=" + type.mType.getName());
+ return null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @SuppressWarnings("unchecked")
+ private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) {
+ ListenersContainer<?> container = mListeners.get(type);
+ if (container == null) {
+ ListenersContainer<T> lc = new ListenersContainer<>();
+ mListeners.put(type, lc);
+ return lc;
+ } else {
+ return (ListenersContainer<T>) container;
+ }
+ }
+
+ private static final class ListenersContainer<T> {
+ private final Set<PluginManager.PluginChangeListener<T>> mListeners = new LinkedHashSet<>();
+ }
+}
diff --git a/services/core/java/com/android/server/display/plugin/PluginType.java b/services/core/java/com/android/server/display/plugin/PluginType.java
new file mode 100644
index 000000000000..fb60833d259e
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin;
+
+import com.android.internal.annotations.Keep;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Represent customisation entry point to Framework. OEM and Framework team should define
+ * new PluginTypes together, after that, Framework team can integrate listener and OEM team
+ * create Plugin implementation
+ *
+ * @param <T> type of plugin value
+ */
+@Keep
+public class PluginType<T> {
+
+ final Class<T> mType;
+ final String mName;
+
+ @VisibleForTesting
+ PluginType(Class<T> type, String name) {
+ mType = type;
+ mName = name;
+ }
+
+ @Override
+ public String toString() {
+ return "PluginType{"
+ + "mType=" + mType
+ + ", mName=" + mName
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/display/plugin/PluginsProvider.java b/services/core/java/com/android/server/display/plugin/PluginsProvider.java
new file mode 100644
index 000000000000..9ad85f67bc8b
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginsProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+import java.util.List;
+
+/**
+ * Interface that OEMs should implement in order to supply Plugins to PluginManager
+ */
+@KeepForApi
+public interface PluginsProvider {
+ /**
+ * Provides list of Plugins to PluginManager
+ */
+ @NonNull
+ List<Plugin> getPlugins(Context context, PluginStorage storage);
+}
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index dba687413496..0b46e0fc3268 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -61,7 +61,7 @@ public class DisplayStateController {
// We might override this below based on other factors.
// Initialise brightness as invalid.
int state;
- int reason = Display.STATE_REASON_DEFAULT_POLICY;
+ int reason = displayPowerRequest.policyReason;
switch (displayPowerRequest.policy) {
case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF:
state = Display.STATE_OFF;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 19305dedcb06..0c04be10d06d 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall;
import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
@@ -64,12 +65,15 @@ import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.DumpUtils;
@@ -86,6 +90,7 @@ import java.io.PrintWriter;
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;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -155,6 +160,10 @@ public final class DreamManagerService extends SystemService {
private ComponentName mDreamOverlayServiceName;
private final AmbientDisplayConfiguration mDozeConfig;
+
+ /** Stores {@link PerUserPackageMonitor} to monitor dream uninstalls. */
+ private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@@ -218,6 +227,15 @@ public final class DreamManagerService extends SystemService {
}
}
+ private final class PerUserPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ super.onPackageRemoved(packageName, uid);
+ final int userId = getChangingUserId();
+ updateDreamOnPackageRemoved(packageName, userId);
+ }
+ }
+
public DreamManagerService(Context context) {
this(context, new DreamHandler(FgThread.get().getLooper()));
}
@@ -333,6 +351,37 @@ public final class DreamManagerService extends SystemService {
});
}
+ @Override
+ public void onUserStarting(@NonNull TargetUser user) {
+ super.onUserStarting(user);
+ if (cleanupDreamSettingsOnUninstall()) {
+ mHandler.post(() -> {
+ final int userId = user.getUserIdentifier();
+ if (!mPackageMonitors.contains(userId)) {
+ final PackageMonitor monitor = new PerUserPackageMonitor();
+ monitor.register(mContext, UserHandle.of(userId), mHandler);
+ mPackageMonitors.put(userId, monitor);
+ } else {
+ Slog.w(TAG, "Package monitor already registered for " + userId);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onUserStopping(@NonNull TargetUser user) {
+ super.onUserStopping(user);
+ if (cleanupDreamSettingsOnUninstall()) {
+ mHandler.post(() -> {
+ final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
+ user.getUserIdentifier());
+ if (monitor != null) {
+ monitor.unregister();
+ }
+ });
+ }
+ }
+
private void dumpInternal(PrintWriter pw) {
synchronized (mLock) {
pw.println("DREAM MANAGER (dumpsys dreams)");
@@ -605,7 +654,7 @@ public final class DreamManagerService extends SystemService {
private ComponentName chooseDreamForUser(boolean doze, int userId) {
if (doze) {
ComponentName dozeComponent = getDozeComponent(userId);
- return validateDream(dozeComponent) ? dozeComponent : null;
+ return validateDream(dozeComponent, userId) ? dozeComponent : null;
}
if (mSystemDreamComponent != null) {
@@ -616,11 +665,11 @@ public final class DreamManagerService extends SystemService {
return dreams != null && dreams.length != 0 ? dreams[0] : null;
}
- private boolean validateDream(ComponentName component) {
+ private boolean validateDream(ComponentName component, int userId) {
if (component == null) return false;
- final ServiceInfo serviceInfo = getServiceInfo(component);
+ final ServiceInfo serviceInfo = getServiceInfo(component, userId);
if (serviceInfo == null) {
- Slog.w(TAG, "Dream " + component + " does not exist");
+ Slog.w(TAG, "Dream " + component + " does not exist on user " + userId);
return false;
} else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
&& !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
@@ -647,7 +696,7 @@ public final class DreamManagerService extends SystemService {
List<ComponentName> validComponents = new ArrayList<>();
if (components != null) {
for (ComponentName component : components) {
- if (validateDream(component)) {
+ if (validateDream(component, userId)) {
validComponents.add(component);
}
}
@@ -664,6 +713,30 @@ public final class DreamManagerService extends SystemService {
return validComponents.toArray(new ComponentName[validComponents.size()]);
}
+ private void updateDreamOnPackageRemoved(String packageName, int userId) {
+ final ComponentName[] componentNames = componentsFromString(
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_COMPONENTS,
+ userId));
+ if (componentNames != null) {
+ // Filter out any components in the removed package.
+ final ComponentName[] filteredComponents =
+ Arrays.stream(componentNames)
+ .filter((componentName -> !isSamePackage(packageName, componentName)))
+ .toArray(ComponentName[]::new);
+ if (filteredComponents.length != componentNames.length) {
+ setDreamComponentsForUser(userId, filteredComponents);
+ }
+ }
+ }
+
+ private static boolean isSamePackage(String packageName, ComponentName componentName) {
+ if (packageName == null || componentName == null) {
+ return false;
+ }
+ return TextUtils.equals(componentName.getPackageName(), packageName);
+ }
+
private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_COMPONENTS,
@@ -718,9 +791,10 @@ public final class DreamManagerService extends SystemService {
return userId == mainUserId;
}
- private ServiceInfo getServiceInfo(ComponentName name) {
+ private ServiceInfo getServiceInfo(ComponentName name, int userId) {
+ final Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
try {
- return name != null ? mContext.getPackageManager().getServiceInfo(name,
+ return name != null ? userContext.getPackageManager().getServiceInfo(name,
PackageManager.MATCH_DEBUG_TRIAGED_MISSING) : null;
} catch (NameNotFoundException e) {
return null;
@@ -813,7 +887,7 @@ public final class DreamManagerService extends SystemService {
private void writePulseGestureEnabled() {
ComponentName name = getDozeComponent();
- boolean dozeEnabled = validateDream(name);
+ boolean dozeEnabled = validateDream(name, ActivityManager.getCurrentUser());
LocalServices.getService(InputManagerInternal.class).setPulseGestureEnabled(dozeEnabled);
}
@@ -823,7 +897,10 @@ public final class DreamManagerService extends SystemService {
}
StringBuilder names = new StringBuilder();
for (ComponentName componentName : componentNames) {
- if (names.length() > 0) {
+ if (componentName == null) {
+ continue;
+ }
+ if (!names.isEmpty()) {
names.append(',');
}
names.append(componentName.flattenToString());
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
index 2f817dbb9a7f..345366882d5a 100644
--- a/services/core/java/com/android/server/flags/pinner.aconfig
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -9,8 +9,11 @@ flag {
}
flag {
- name: "skip_home_art_pins"
- namespace: "system_performance"
- description: "Ablation study flag that controls if home app odex/vdex files should be pinned in memory."
- bug: "340935152"
-} \ No newline at end of file
+ name: "pin_global_quota"
+ namespace: "system_performance"
+ description: "This flag controls whether pinner will use a global quota or not"
+ bug: "340935152"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 69ba785b3b4f..eea5c982c537 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -67,3 +67,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "backstage_power"
+ name: "rate_limit_battery_changed_broadcast"
+ description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates"
+ bug: "362337621"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index b78f8a7d8ee7..a06f9ef634d1 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -509,6 +509,28 @@ final class Constants {
static final String PROPERTY_STRIP_AUDIO_TV_NO_SYSTEM_AUDIO =
"persist.sys.hdmi.property_strip_audio_tv_no_system_audio";
+ /**
+ * Property that decides whether CEC should be disabled on standby when the low energy mode
+ * option is used.
+ */
+ static final String PROPERTY_WAS_CEC_DISABLED_ON_STANDBY_BY_LOW_ENERGY_MODE =
+ "persist.sys.hdmi.property_was_cec_disabled_on_standby_by_low_energy_mode";
+
+ /**
+ * Property that checks if CEC was disabled on standby by low energy mode. With the help of this
+ * property we avoid re-enabling CEC if the user explicitly disabled it, unrelated to the
+ * selected energy mode.
+ */
+ static final String PROPERTY_DISABLE_CEC_ON_STANDBY_IN_LOW_ENERGY_MODE =
+ "persist.sys.hdmi.property_disable_cec_on_standby_in_low_energy_mode";
+
+ /**
+ * Property that checks if CEC was manually enabled by the user in offline mode. With the help
+ * of this property we avoid turning off CEC when the device goes to sleep and if the device
+ * is in low energy mode.
+ */
+ static final String PROPERTY_USER_ACTION_KEEP_CEC_ENABLED_IN_OFFLINE_MODE =
+ "persist.sys.hdmi.property_user_action_keep_cec_enabled_in_offline_mode";
static final int RECORDING_TYPE_DIGITAL_RF = 1;
static final int RECORDING_TYPE_ANALOGUE_RF = 2;
static final int RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS = 3;
@@ -644,6 +666,11 @@ final class Constants {
})
@interface FeatureFlag {}
+ /**
+ * Identifier key for Low energy mode.
+ */
+ static final String KEY_LOW_ENERGY_USE = "low_energy_use";
+
private Constants() {
/* cannot be instantiated */
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 53c02176f11d..102de73374b7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.media.tv.flags.Flags.hdmiControlCollectPhysicalAddress;
+
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
@@ -35,6 +37,8 @@ public class HdmiCecAtomWriter {
@VisibleForTesting
protected static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
private static final int ERROR_CODE_UNKNOWN = -1;
+ @VisibleForTesting
+ protected static final int PHYSICAL_ADDRESS_INVALID = 0xFFFF;
/**
* Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
@@ -95,6 +99,11 @@ public class HdmiCecAtomWriter {
return createUserControlPressedSpecialArgs(message);
case Constants.MESSAGE_FEATURE_ABORT:
return createFeatureAbortSpecialArgs(message);
+ case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ if (hdmiControlCollectPhysicalAddress()) {
+ return createReportPhysicalAddressSpecialArgs(message);
+ }
+ return new MessageReportedSpecialArgs();
default:
return new MessageReportedSpecialArgs();
}
@@ -140,6 +149,23 @@ public class HdmiCecAtomWriter {
}
/**
+ * Constructs the special arguments for a <Report Physical Address> message.
+ *
+ * @param message The HDMI CEC message to log
+ */
+ private MessageReportedSpecialArgs createReportPhysicalAddressSpecialArgs(
+ HdmiCecMessage message) {
+ MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
+
+ if (message.getParams().length > 1) {
+ int physicalAddress = (message.getParams()[0] << 8) | message.getParams()[1];
+ specialArgs.mPhysicalAddress = physicalAddress;
+ }
+
+ return specialArgs;
+ }
+
+ /**
* Writes a HdmiCecMessageReported atom.
*
* @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
@@ -156,7 +182,8 @@ public class HdmiCecAtomWriter {
genericArgs.mSendMessageResult,
specialArgs.mUserControlPressedCommand,
specialArgs.mFeatureAbortOpcode,
- specialArgs.mFeatureAbortReason);
+ specialArgs.mFeatureAbortReason,
+ specialArgs.mPhysicalAddress);
}
/**
@@ -166,7 +193,7 @@ public class HdmiCecAtomWriter {
protected void writeHdmiCecMessageReportedAtom(int uid, int direction,
int initiatorLogicalAddress, int destinationLogicalAddress, int opcode,
int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode,
- int featureAbortReason) {
+ int featureAbortReason, int physicalAddress) {
FrameworkStatsLog.write(
FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
uid,
@@ -177,7 +204,8 @@ public class HdmiCecAtomWriter {
sendMessageResult,
userControlPressedCommand,
featureAbortOpcode,
- featureAbortReason);
+ featureAbortReason,
+ physicalAddress);
}
/**
@@ -237,6 +265,26 @@ public class HdmiCecAtomWriter {
enumLogReason);
}
+ /**
+ * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+ * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+ * @param isEnabled Whether the setting is enabled.
+ * @param enumLogReason The event that triggered the log.
+ * @param manufacturerPnpId Manufacturer PNP ID reported in the EDID.
+ * @param manufacturerYear Manufacture year reported in the EDID.
+ * @param manufacturerWeek Manufacture week reporter in the EDID.
+ */
+ public void powerStateChangeOnActiveSourceLostChanged(boolean isEnabled, int enumLogReason,
+ String manufacturerPnpId, int manufacturerYear, int manufacturerWeek) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.HDMI_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLED,
+ isEnabled,
+ enumLogReason,
+ manufacturerPnpId,
+ manufacturerYear,
+ manufacturerWeek);
+ }
+
private int earcStateToEnum(int earcState) {
switch (earcState) {
case HDMI_EARC_STATUS_IDLE:
@@ -284,5 +332,6 @@ public class HdmiCecAtomWriter {
int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
+ int mPhysicalAddress = PHYSICAL_ADDRESS_INVALID;
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f2e2f653f929..5b4c0337862b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -1001,7 +1001,7 @@ final class HdmiCecController {
try {
// Create an AIDL callback that can callback onHotplugEvent
mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback));
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e);
}
}
@@ -1134,7 +1134,7 @@ final class HdmiCecController {
i++;
}
return hdmiPortInfo;
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error("Failed to get port information : ", e);
return null;
}
@@ -1144,7 +1144,7 @@ final class HdmiCecController {
public boolean nativeIsConnected(int port) {
try {
return mHdmiConnection.isConnected(port);
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error("Failed to get connection info : ", e);
return false;
}
@@ -1158,7 +1158,7 @@ final class HdmiCecController {
HdmiLogger.error(
"Could not set HPD signal type for portId " + portId + " to " + signal
+ ". Error: ", sse.errorCode);
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error(
"Could not set HPD signal type for portId " + portId + " to " + signal
+ ". Exception: ", e);
@@ -1169,7 +1169,7 @@ final class HdmiCecController {
public int nativeGetHpdSignalType(int portId) {
try {
return mHdmiConnection.getHpdSignal(portId);
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error(
"Could not get HPD signal type for portId " + portId + ". Exception: ", e);
return Constants.HDMI_HPD_TYPE_PHYSICAL;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d0ad6fc0854f..0b667fc10880 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -21,6 +21,7 @@ import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.hardware.display.DeviceProductInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -31,6 +32,7 @@ import android.os.PowerManager;
import android.os.SystemProperties;
import android.sysprop.HdmiProperties;
import android.util.Slog;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
@@ -82,6 +84,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
// lost.
private Handler mDelayedPopupOnActiveSourceLostHandler;
+ private boolean mIsActiveSourceLostPopupLaunched;
+
// Determines what action should be taken upon receiving Routing Control messages.
@VisibleForTesting
protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -96,6 +100,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
mDelayedStandbyOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
mDelayedPopupOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
+ mIsActiveSourceLostPopupLaunched = false;
}
@Override
@@ -273,13 +278,9 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
@Override
public void run() {
- if (mService.getPowerManagerInternal().wasDeviceIdleFor(
- STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) {
+ if (!isActiveSource()) {
mService.standby();
- } else {
- mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
- getDeviceInfo().getDeviceType(), Constants.ADDR_TV,
- "DelayedActiveSourceLostStandbyRunnable");
+ mIsActiveSourceLostPopupLaunched = false;
}
}
}
@@ -288,6 +289,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
void dismissUiOnActiveSourceStatusRecovered() {
assertRunOnServiceThread();
Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
+ mIsActiveSourceLostPopupLaunched = false;
mService.sendBroadcastAsUser(intent);
}
@@ -444,14 +446,62 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
new Runnable() {
@Override
public void run() {
- if (!isActiveSource()) {
+ if (isActiveSource()) {
+ return;
+ }
+
+ if (getActiveSource().logicalAddress != Constants.ADDR_TV) {
startHdmiCecActiveSourceLostActivity();
mDelayedStandbyOnActiveSourceLostHandler
.removeCallbacksAndMessages(null);
mDelayedStandbyOnActiveSourceLostHandler.postDelayed(
new DelayedStandbyOnActiveSourceLostRunnable(),
STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ return;
}
+
+ // We observed specific TV panels (old models) that send faulty CEC
+ // source changing messages, especially during wake-up.
+ // This request helps to check if the TV correctly asserted active
+ // source or not. If the request times out, active source is
+ // asserted by the local device.
+ addAndStartAction(new RequestActiveSourceAction(mService.playback(),
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ // If a device answers to <Request Active Source>, the
+ // pop-up should be triggered.
+ // During this action, the TV can switch to an HDMI input
+ // with a non-CEC capable device that won't be able to
+ // answer this request.
+ // In this case, the known active source would be
+ // represented by a valid physical address, but invalid
+ // logical address. The pop-up will be shown and the local
+ // device will not assert active source.
+ if (result == HdmiControlManager.RESULT_SUCCESS
+ || getActiveSource().logicalAddress
+ != Constants.ADDR_TV) {
+ startHdmiCecActiveSourceLostActivity();
+ mDelayedStandbyOnActiveSourceLostHandler
+ .removeCallbacksAndMessages(null);
+ mDelayedStandbyOnActiveSourceLostHandler
+ .postDelayed(
+ new DelayedStandbyOnActiveSourceLostRunnable(),
+ STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ } else {
+ // The request times out and the local device is not
+ // active source, but the TV previously asserted active
+ // source.
+ if (getActiveSource().logicalAddress
+ == Constants.ADDR_TV) {
+ mService.setAndBroadcastActiveSource(
+ mService.getPhysicalAddress(),
+ getDeviceInfo().getDeviceType(),
+ Constants.ADDR_BROADCAST,
+ "RequestActiveSourceAction#RESULT_TIMEOUT");
+ }
+ }
+ }}));
}
}, POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
return;
@@ -473,6 +523,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivityAsUser(intent, context.getUser());
+ mIsActiveSourceLostPopupLaunched = true;
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Unable to start HdmiCecActiveSourceLostActivity");
} finally {
@@ -690,6 +741,14 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
return Constants.ADDR_TV;
}
+ boolean isActiveSourceLostPopupLaunched() {
+ return mIsActiveSourceLostPopupLaunched;
+ }
+
+ void setIsActiveSourceLostPopupLaunched(boolean isActiveSourceLostPopupLaunched) {
+ mIsActiveSourceLostPopupLaunched = isActiveSourceLostPopupLaunched;
+ }
+
@Override
@ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
@@ -698,6 +757,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
removeAction(HotplugDetectionAction.class);
removeAction(NewDeviceAction.class);
removeAction(PowerStatusMonitorActionFromPlayback.class);
+ removeAction(RequestActiveSourceAction.class);
super.disableDevice(initiatedByCec, callback);
clearDeviceInfoList();
checkIfPendingActionsCleared();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index aae7b59b1a1a..7505c710f483 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -471,6 +471,10 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
assertRunOnServiceThread();
if (oldPath == newPath) {
+ HdmiCecMessage setStreamPath =
+ HdmiCecMessageBuilder.buildSetStreamPath(getDeviceInfo().getLogicalAddress(),
+ oldPath);
+ mService.sendCecCommand(setStreamPath);
return;
}
HdmiCecMessage routingChange =
@@ -642,8 +646,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
int address = message.getSource();
int type = message.getParams()[2];
- if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
- handleNewDeviceAtTheTailOfActivePath(path);
+ if (getActiveSource().logicalAddress != address && getActivePath() == path) {
+ HdmiLogger.debug("New logical address detected on the current active path.");
+ startRoutingControl(path, path, null);
}
startNewDeviceAction(ActiveSource.of(address, path), type);
return Constants.HANDLED;
@@ -792,6 +797,10 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
+ if (!isInputReady(info.getDeviceId())) {
+ mService.getHdmiCecNetwork().removeCecDevice(
+ HdmiCecLocalDeviceTv.this, info.getLogicalAddress());
+ }
mService.getHdmiCecNetwork().addCecDevice(info);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3fa62e9bc7de..35ef18b144e7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -22,6 +22,7 @@ import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED;
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED;
+import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_DISABLED;
import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
import static android.hardware.hdmi.HdmiControlManager.POWER_CONTROL_MODE_NONE;
import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
@@ -49,6 +50,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
+import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayManager;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
@@ -105,6 +107,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.KeyEvent;
+import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -478,7 +481,8 @@ public class HdmiControlService extends SystemService {
@Nullable
private HdmiCecController mCecController;
- private HdmiCecPowerStatusController mPowerStatusController;
+ @VisibleForTesting
+ protected HdmiCecPowerStatusController mPowerStatusController;
@Nullable
private HdmiEarcController mEarcController;
@@ -723,6 +727,13 @@ public class HdmiControlService extends SystemService {
}
mPowerStatusController.setPowerStatus(getInitialPowerStatus());
setProhibitMode(false);
+ if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+ Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+ + " mode.");
+ mHdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HDMI_CEC_CONTROL_ENABLED);
+ setWasCecDisabledOnStandbyByLowEnergyMode(false);
+ }
mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
@@ -998,6 +1009,21 @@ public class HdmiControlService extends SystemService {
}
}, mServiceThreadExecutor);
+ if (isPlaybackDevice()) {
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ boolean goToStandbyOnActiveSourceLost =
+ mHdmiCecConfig.getStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)
+ .equals(HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ writePowerStateChangeOnActiveSourceLostAtom(goToStandbyOnActiveSourceLost);
+ }
+ }, mServiceThreadExecutor);
+ }
+
mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
new DeviceConfig.OnPropertiesChangedListener() {
@Override
@@ -3176,6 +3202,11 @@ public class HdmiControlService extends SystemService {
HdmiCecLocalDeviceSource source = playback();
if (source == null) {
source = audioSystem();
+ } else {
+ // Cancel an existing timer to send the device to sleep since OTP was triggered.
+ playback().mDelayedStandbyOnActiveSourceLostHandler
+ .removeCallbacksAndMessages(null);
+ playback().setIsActiveSourceLostPopupLaunched(false);
}
if (source == null) {
@@ -3813,7 +3844,32 @@ public class HdmiControlService extends SystemService {
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
false);
if (mCecController != null) {
- if (isCecControlEnabled()) {
+ if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+ Slog.w(TAG, "Re-enable CEC on wake-up since it was disabled due to low energy "
+ + " mode.");
+ getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HDMI_CEC_CONTROL_ENABLED);
+ setWasCecDisabledOnStandbyByLowEnergyMode(false);
+ int controlStateChangedReason = -1;
+ switch (wakeUpAction) {
+ case WAKE_UP_SCREEN_ON:
+ controlStateChangedReason =
+ HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
+ break;
+ case WAKE_UP_BOOT_UP:
+ controlStateChangedReason =
+ HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
+ break;
+ default:
+ Slog.e(TAG, "wakeUpAction " + wakeUpAction + " not defined.");
+ return;
+
+ }
+ // Since CEC is going to be initialized by the setting value update, we must invoke
+ // the vendor command listeners here with the reason TV woke up.
+ invokeVendorCommandListenersOnControlStateChanged(true,
+ controlStateChangedReason);
+ } else if (isCecControlEnabled()) {
int startReason = -1;
switch (wakeUpAction) {
case WAKE_UP_SCREEN_ON:
@@ -3987,6 +4043,15 @@ public class HdmiControlService extends SystemService {
if (isAudioSystemDevice() || !isPowerStandby()) {
return;
}
+ if (isTvDevice() && getDisableCecOnStandbyByLowEnergyMode()
+ && mPowerManager.isLowPowerStandbyEnabled()
+ && !userEnabledCecInOfflineMode()) {
+ Slog.w(TAG, "Disable CEC on standby due to low power energy mode.");
+ setWasCecDisabledOnStandbyByLowEnergyMode(true);
+ getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HDMI_CEC_CONTROL_DISABLED);
+ }
mCecController.enableSystemCecControl(false);
mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
}
@@ -4308,6 +4373,7 @@ public class HdmiControlService extends SystemService {
if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
HdmiCecLocalDevicePlayback playback = playback();
playback.dismissUiOnActiveSourceStatusRecovered();
+ playback.removeAction(RequestActiveSourceAction.class);
playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
caller);
playback.wakeUpIfActiveSource();
@@ -5147,4 +5213,72 @@ public class HdmiControlService extends SystemService {
protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() {
return hdmiControlEnhancedBehavior();
}
+
+ /**
+ * Reads the property value that decides whether CEC should be disabled on standby when the low
+ * energy mode option is used.
+ */
+ @VisibleForTesting
+ protected boolean getDisableCecOnStandbyByLowEnergyMode() {
+ return SystemProperties.getBoolean(
+ Constants.PROPERTY_DISABLE_CEC_ON_STANDBY_IN_LOW_ENERGY_MODE, false);
+ }
+
+ /**
+ * Reads the property that checks if CEC was disabled on standby by low energy mode.
+ */
+ @VisibleForTesting
+ protected boolean getWasCecDisabledOnStandbyByLowEnergyMode() {
+ return SystemProperties.getBoolean(
+ Constants.PROPERTY_WAS_CEC_DISABLED_ON_STANDBY_BY_LOW_ENERGY_MODE, false);
+ }
+
+ /**
+ * Sets the truth value of the property that checks if CEC was disabled on standby by low energy
+ * mode.
+ */
+ @VisibleForTesting
+ protected void setWasCecDisabledOnStandbyByLowEnergyMode(boolean value) {
+ writeStringSystemProperty(
+ Constants.PROPERTY_WAS_CEC_DISABLED_ON_STANDBY_BY_LOW_ENERGY_MODE,
+ String.valueOf(value));
+ }
+
+ /**
+ * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+ * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+ */
+ protected void writePowerStateChangeOnActiveSourceLostAtom(boolean isSettingEnabled) {
+ String manufacturerPnpId = "undefined";
+ int manufactureYear = -1;
+ int manufactureWeek = -1;
+ Display display = getContext().getDisplay();
+ if (display != null) {
+ DeviceProductInfo deviceProductInfo = display.getDeviceProductInfo();
+ manufacturerPnpId = deviceProductInfo.getManufacturerPnpId();
+ manufactureYear = deviceProductInfo.getManufactureYear();
+ }
+ int enumLogReason =
+ HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_UNKNOWN;
+ if (playback() != null) {
+ if (playback().isActiveSourceLostPopupLaunched()) {
+ enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP;
+ } else {
+ enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING;
+ }
+ }
+
+ getAtomWriter().powerStateChangeOnActiveSourceLostChanged(isSettingEnabled, enumLogReason,
+ manufacturerPnpId, manufactureYear, manufactureWeek);
+ }
+
+ /**
+ * Reads the property that checks if CEC was enabled by the user while in offline mode such that
+ * it won't be disabled when going to sleep by low energy mode.
+ */
+ @VisibleForTesting
+ protected boolean userEnabledCecInOfflineMode() {
+ return SystemProperties.getBoolean(
+ Constants.PROPERTY_USER_ACTION_KEEP_CEC_ENABLED_IN_OFFLINE_MODE, false);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java b/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java
index 7530b3b239b4..5292cbbb9336 100644
--- a/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static com.android.server.hdmi.Constants.KEY_LOW_ENERGY_USE;
+
import android.content.Context;
import android.os.PowerManager;
@@ -47,6 +49,12 @@ public class PowerManagerWrapper {
return new DefaultWakeLockWrapper(mPowerManager.newWakeLock(levelAndFlags, tag));
}
+ boolean isLowPowerStandbyEnabled() {
+ PowerManager.LowPowerStandbyPolicy lowPowerStandbyPolicy
+ = mPowerManager.getLowPowerStandbyPolicy();
+ return lowPowerStandbyPolicy.getIdentifier().equals(KEY_LOW_ENERGY_USE);
+ }
+
/**
* "Default" wrapper for {@link PowerManager.WakeLock}, as opposed to a "Fake" wrapper for
* testing - see {@link FakePowerManagerWrapper.FakeWakeLockWrapper}.
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index a33d70a9b876..f9a1cebc952f 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -22,11 +22,11 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
/**
- * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
- * panels.
- * This action has a delay before sending <Request Active Source>. This is because it should wait
- * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
- * received or the TV switched to another input.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ *
+ * For TV panels, this action has a delay before sending <Request Active Source>. This is because it
+ * should wait for a possible request from LauncherX or TIF (TV Input Framework) and can be
+ * cancelled if an <Active Source> message was received or the TV switched to another input.
*/
public class RequestActiveSourceAction extends HdmiCecFeatureAction {
private static final String TAG = "RequestActiveSourceAction";
@@ -40,9 +40,9 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction {
// Number of retries <Request Active Source> is sent if no device answers this message.
private static final int MAX_SEND_RETRY_COUNT = 1;
- // Timeout to wait for the LauncherX API call to be completed.
+ // Timeout to wait for LauncherX or TIF to call the CEC API.
@VisibleForTesting
- protected static final int TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS = 10000;
+ protected static final int TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS = 15000;
private int mSendRetryCount = 0;
@@ -55,12 +55,19 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction {
boolean start() {
Slog.v(TAG, "RequestActiveSourceAction started.");
+ if (localDevice().mService.isPlaybackDevice()) {
+ mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
+ sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ return true;
+ }
+
mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
// We wait for default timeout to allow the message triggered by the LauncherX API call to
// be sent by the TV and another default timeout in case the message has to be answered
// (e.g. TV sent a <Set Stream Path> or <Routing Change>).
- addTimer(mState, TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
+ addTimer(mState, TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
return true;
}
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index adcda0a63152..9a6c87e525e0 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -304,16 +304,16 @@ class PendingReports {
denyReportBeforeAddingRec(listener, callingPackage);
return;
}
+ AttributionSource attributionSource =
+ new AttributionSource.Builder(callingUid)
+ .setPackageName(callingPackage)
+ .build();
// Only with userdebug/eng build: it could check capture consentless bugreport permission
// and approve the report when it's granted.
boolean captureConsentlessBugreportOnUserdebugBuildGranted = false;
if ((Build.IS_USERDEBUG || Build.IS_ENG)
&& (flags & IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT) != 0) {
- AttributionSource attributionSource =
- new AttributionSource.Builder(callingUid)
- .setPackageName(callingPackage)
- .build();
captureConsentlessBugreportOnUserdebugBuildGranted =
mPermissionManager.checkPermissionForDataDelivery(
Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
@@ -321,12 +321,28 @@ class PendingReports {
/* message= */ null)
== PERMISSION_GRANTED;
}
- if (captureConsentlessBugreportOnUserdebugBuildGranted) {
+
+ // Allow system apps to skip the consent dialog and use their in-built consent mechanism
+ // instead.
+ boolean captureConsentlessBugreportDelegatedConsentGranted =
+ mPermissionManager.checkPermissionForDataDelivery(
+ Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT,
+ attributionSource,
+ /* message= */ null)
+ == PERMISSION_GRANTED;
+
+ if (captureConsentlessBugreportOnUserdebugBuildGranted
+ || captureConsentlessBugreportDelegatedConsentGranted) {
try {
PendingReportRec rec =
new PendingReportRec(
callingPackage, receiverClass, reportId, flags, listener);
- Log.d(TAG, "approving consentless report: " + rec.getUri());
+ if (captureConsentlessBugreportOnUserdebugBuildGranted) {
+ Log.d(TAG, "approving consentless report: " + rec.getUri());
+ }
+ if (captureConsentlessBugreportDelegatedConsentGranted) {
+ Log.d(TAG, "delegating consent for report: " + rec.getUri());
+ }
listener.onReportApproved();
return;
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/incident/TEST_MAPPING b/services/core/java/com/android/server/incident/TEST_MAPPING
new file mode 100644
index 000000000000..4f789dbba2b8
--- /dev/null
+++ b/services/core/java/com/android/server/incident/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "postsubmit": [
+ {
+ "name": "CtsRootBugreportTestCases"
+ },
+ {
+ "name": "BugreportManagerTestCases"
+ }
+ ]
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
new file mode 100644
index 000000000000..8c028bc92841
--- /dev/null
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.KeyGestureEvent;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.R;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Manages quick launch app shortcuts by parsing {@code bookmarks.xml} and intercepting the
+ * correct key combinations for the app shortcuts defined.
+ *
+ * Currently there are 2 ways of defining shortcuts:
+ * - Adding shortcuts to {@code bookmarks.xml}
+ * - Calling into {@code registerShortcutKey()}.
+ */
+final class AppLaunchShortcutManager {
+ private static final String TAG = "AppShortcutManager";
+
+ private static final String TAG_BOOKMARKS = "bookmarks";
+ private static final String TAG_BOOKMARK = "bookmark";
+
+ private static final String ATTRIBUTE_PACKAGE = "package";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_CATEGORY = "category";
+ private static final String ATTRIBUTE_SHIFT = "shift";
+ private static final String ATTRIBUTE_ROLE = "role";
+
+ private static final int SHORTCUT_CODE_META_MASK =
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+ | KeyEvent.META_META_ON;
+
+ private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+ /* Table of Application Launch keys. Maps from key codes to intent categories.
+ *
+ * These are special keys that are used to launch particular kinds of applications,
+ * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C)
+ * usage page. We don't support quite that many yet...
+ */
+ private static final SparseArray<String> sApplicationLaunchKeyCategories;
+ private static final SparseArray<String> sApplicationLaunchKeyRoles;
+ static {
+ sApplicationLaunchKeyRoles = new SparseArray<>();
+ sApplicationLaunchKeyCategories = new SparseArray<>();
+ sApplicationLaunchKeyRoles.append(
+ KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+ }
+
+ private final Context mContext;
+ private boolean mSearchKeyShortcutPending = false;
+ private boolean mConsumeSearchKeyUp = true;
+ private final Map<InputGestureData.Trigger, InputGestureData> mBookmarks = new HashMap<>();
+
+ @SuppressLint("MissingPermission")
+ AppLaunchShortcutManager(Context context) {
+ mContext = context;
+ }
+
+ public void systemRunning() {
+ loadShortcuts();
+ }
+
+ private void loadShortcuts() {
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!TAG_BOOKMARK.equals(parser.getName())) {
+ Log.w(TAG, "TAG_BOOKMARK not found");
+ break;
+ }
+
+ String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+ String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+ String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+ String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+ String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+ String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+ int keycode;
+ int modifierState;
+ TypedArray a = mContext.getResources().obtainAttributes(parser,
+ R.styleable.Bookmark);
+ try {
+ keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+ modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+ } finally {
+ a.recycle();
+ }
+ if (keycode == KeyEvent.KEYCODE_UNKNOWN && !TextUtils.isEmpty(shortcut)) {
+ // Fetch keycode using shortcut char
+ KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
+ Locale.ROOT).charAt(0)});
+ // Single key press can generate the character
+ if (events != null && events.length == 2) {
+ keycode = events[0].getKeyCode();
+ }
+ }
+ if (keycode == KeyEvent.KEYCODE_UNKNOWN) {
+ Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+ + " packageName=" + packageName + " className=" + className
+ + " role=" + roleName + " shiftName=" + shiftName
+ + " shortcut=" + shortcut + " modifierState=" + modifierState);
+ continue;
+ }
+
+ if (modifierState == 0) {
+ // Fetch modifierState using shiftName
+ boolean isShiftShortcut = shiftName != null && shiftName.toLowerCase(
+ Locale.ROOT).equals("true");
+ modifierState =
+ KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+ }
+ AppLaunchData launchData = null;
+ if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+ launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
+ } else if (!TextUtils.isEmpty(categoryName)) {
+ launchData = AppLaunchData.createLaunchDataForCategory(categoryName);
+ } else if (!TextUtils.isEmpty(roleName)) {
+ launchData = AppLaunchData.createLaunchDataForRole(roleName);
+ }
+ if (launchData != null) {
+ Log.d(TAG, "adding shortcut " + launchData + " modifierState="
+ + modifierState + " keycode=" + keycode);
+ // All bookmarks are based on Action key
+ InputGestureData bookmark = new InputGestureData.Builder()
+ .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(launchData)
+ .build();
+ mBookmarks.put(bookmark.getTrigger(), bookmark);
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Got exception parsing bookmarks.", e);
+ }
+ }
+
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+ throws RemoteException {
+ IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+ if (service != null && service.asBinder().pingBinder()) {
+ throw new RemoteException("Key: " + shortcutCode + ", already exists.");
+ }
+
+ mShortcutKeyServices.put(shortcutCode, shortcutService);
+ }
+
+ /**
+ * Handle the shortcut to {@link IShortcutService}
+ * @return true if invoked the shortcut, otherwise false.
+ */
+ public boolean handleShortcutService(KeyEvent event) {
+ // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either
+ // migrated to bookmarks or customizable shortcut APIs.
+ final long shortcutCodeMeta = event.getMetaState() & SHORTCUT_CODE_META_MASK;
+ if (shortcutCodeMeta == 0) {
+ return false;
+ }
+ long shortcutCode = event.getKeyCode() | (shortcutCodeMeta << Integer.SIZE);
+ IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+ if (shortcutService != null) {
+ try {
+ shortcutService.notifyShortcutKeyPressed(shortcutCode);
+ } catch (RemoteException e) {
+ Log.w(TAG,
+ "Shortcut key service not found, deleting shortcut code: " + shortcutCode);
+ mShortcutKeyServices.delete(shortcutCode);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle the shortcut to Launch application.
+ *
+ * @param keyEvent The key event.
+ */
+ @SuppressLint("MissingPermission")
+ @Nullable
+ private AppLaunchData interceptShortcut(KeyEvent keyEvent) {
+ final int keyCode = keyEvent.getKeyCode();
+ final int modifierState = keyEvent.getMetaState() & SHORTCUT_CODE_META_MASK;
+ // Shortcuts are invoked through Search+key, so intercept those here
+ // Any printing key that is chorded with Search should be consumed
+ // even if no shortcut was invoked. This prevents text from being
+ // inadvertently inserted when using a keyboard that has built-in macro
+ // shortcut keys (that emit Search+x) and some of them are not registered.
+ if (mSearchKeyShortcutPending) {
+ KeyCharacterMap kcm = keyEvent.getKeyCharacterMap();
+ if (kcm != null && kcm.isPrintingKey(keyCode)) {
+ mConsumeSearchKeyUp = true;
+ mSearchKeyShortcutPending = false;
+ } else {
+ return null;
+ }
+ } else if (modifierState == 0) {
+ AppLaunchData appLaunchData = null;
+ // Handle application launch keys.
+ String role = sApplicationLaunchKeyRoles.get(keyCode);
+ String category = sApplicationLaunchKeyCategories.get(keyCode);
+ if (!TextUtils.isEmpty(role)) {
+ appLaunchData = AppLaunchData.createLaunchDataForRole(role);
+ } else if (!TextUtils.isEmpty(category)) {
+ appLaunchData = AppLaunchData.createLaunchDataForCategory(category);
+ }
+
+ return appLaunchData;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ InputGestureData gesture = mBookmarks.get(
+ InputGestureData.createKeyTrigger(keyCode, modifierState));
+ if (gesture == null) {
+ return null;
+ }
+ return gesture.getAction().appLaunchData();
+ }
+
+ /**
+ * Handle the shortcut from {@link KeyEvent}
+ *
+ * @param event Description of the key event.
+ */
+ public InterceptKeyResult interceptKey(KeyEvent event) {
+ if (event.getRepeatCount() != 0) {
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mSearchKeyShortcutPending = true;
+ mConsumeSearchKeyUp = false;
+ } else {
+ mSearchKeyShortcutPending = false;
+ if (mConsumeSearchKeyUp) {
+ mConsumeSearchKeyUp = false;
+ return InterceptKeyResult.CONSUME_KEY;
+ }
+ }
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ // Intercept shortcuts defined in bookmarks or through application launch keycodes
+ return new InterceptKeyResult(/* consumed =*/ false, interceptShortcut(event));
+ }
+
+ /**
+ * @return a list of {@link InputGestureData} containing the application launch shortcuts parsed
+ * at boot time from {@code bookmarks.xml}.
+ */
+ public List<InputGestureData> getBookmarks() {
+ return new ArrayList<>(mBookmarks.values());
+ }
+
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("AppLaunchShortcutManager:");
+ ipw.increaseIndent();
+ for (InputGestureData data : mBookmarks.values()) {
+ ipw.println(data);
+ }
+ ipw.decreaseIndent();
+ }
+
+ public record InterceptKeyResult(boolean consumed, @Nullable AppLaunchData appLaunchData) {
+ private static final InterceptKeyResult DO_NOTHING = new InterceptKeyResult(false, null);
+ private static final InterceptKeyResult CONSUME_KEY = new InterceptKeyResult(true, null);
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputDataStore.java b/services/core/java/com/android/server/input/InputDataStore.java
new file mode 100644
index 000000000000..e8f21fe8fb74
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputDataStore.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages persistent state recorded by the input manager service as a set of XML files.
+ * Caller must acquire lock on the data store before accessing it.
+ */
+public final class InputDataStore {
+ private static final String TAG = "InputDataStore";
+
+ private static final String INPUT_MANAGER_DIRECTORY = "input";
+
+ private static final String TAG_ROOT = "root";
+
+ private static final String TAG_INPUT_GESTURE_LIST = "input_gesture_list";
+ private static final String TAG_INPUT_GESTURE = "input_gesture";
+ private static final String TAG_KEY_TRIGGER = "key_trigger";
+ private static final String TAG_TOUCHPAD_TRIGGER = "touchpad_trigger";
+ private static final String TAG_APP_LAUNCH_DATA = "app_launch_data";
+
+ private static final String ATTR_KEY_TRIGGER_KEYCODE = "keycode";
+ private static final String ATTR_KEY_TRIGGER_MODIFIER_STATE = "modifiers";
+ private static final String ATTR_KEY_GESTURE_TYPE = "key_gesture_type";
+ private static final String ATTR_TOUCHPAD_TRIGGER_GESTURE_TYPE = "touchpad_gesture_type";
+ private static final String ATTR_APP_LAUNCH_DATA_CATEGORY = "category";
+ private static final String ATTR_APP_LAUNCH_DATA_ROLE = "role";
+ private static final String ATTR_APP_LAUNCH_DATA_PACKAGE_NAME = "package_name";
+ private static final String ATTR_APP_LAUNCH_DATA_CLASS_NAME = "class_name";
+
+ private final FileInjector mInputGestureFileInjector;
+
+ public InputDataStore() {
+ this(new FileInjector("input_gestures.xml"));
+ }
+
+ public InputDataStore(final FileInjector inputGestureFileInjector) {
+ mInputGestureFileInjector = inputGestureFileInjector;
+ }
+
+ /**
+ * Reads from the local disk storage the list of customized input gestures.
+ *
+ * @param userId The user id to fetch the gestures for.
+ * @return List of {@link InputGestureData} which the user previously customized.
+ */
+ public List<InputGestureData> loadInputGestures(int userId) {
+ List<InputGestureData> inputGestureDataList;
+ try {
+ final InputStream inputStream = mInputGestureFileInjector.openRead(userId);
+ inputGestureDataList = readInputGesturesXml(inputStream, false);
+ inputStream.close();
+ } catch (IOException exception) {
+ // In case we are unable to read from the file on disk or another IO operation error,
+ // fail gracefully.
+ Slog.e(TAG, "Failed to read from " + mInputGestureFileInjector.getAtomicFileForUserId(
+ userId), exception);
+ return List.of();
+ } catch (Exception exception) {
+ // In the case of any other exception, we want it to bubble up as this would be due
+ // to malformed trusted XML data.
+ throw new RuntimeException(
+ "Failed to read from " + mInputGestureFileInjector.getAtomicFileForUserId(
+ userId), exception);
+ }
+ return inputGestureDataList;
+ }
+
+ /**
+ * Writes to the local disk storage the list of customized input gestures provided as a param.
+ *
+ * @param userId The user id to store the {@link InputGestureData} list under.
+ * @param inputGestureDataList The list of custom input gestures for the given {@code userId}.
+ */
+ public void saveInputGestures(int userId, List<InputGestureData> inputGestureDataList) {
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = mInputGestureFileInjector.startWrite(userId);
+ writeInputGestureXml(outputStream, false, inputGestureDataList);
+ mInputGestureFileInjector.finishWrite(userId, outputStream, true);
+ } catch (IOException e) {
+ Slog.e(TAG,
+ "Failed to write to file " + mInputGestureFileInjector.getAtomicFileForUserId(
+ userId), e);
+ mInputGestureFileInjector.finishWrite(userId, outputStream, false);
+ }
+ }
+
+ @VisibleForTesting
+ List<InputGestureData> readInputGesturesXml(InputStream stream, boolean utf8Encoded)
+ throws XmlPullParserException, IOException {
+ List<InputGestureData> inputGestureDataList = new ArrayList<>();
+ TypedXmlPullParser parser;
+ if (utf8Encoded) {
+ parser = Xml.newFastPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ } else {
+ parser = Xml.resolvePullParser(stream);
+ }
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String tag = parser.getName();
+ if (TAG_ROOT.equals(tag)) {
+ continue;
+ }
+
+ if (TAG_INPUT_GESTURE_LIST.equals(tag)) {
+ inputGestureDataList.addAll(readInputGestureListFromXml(parser));
+ }
+ }
+ return inputGestureDataList;
+ }
+
+ private InputGestureData readInputGestureFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException, IllegalArgumentException {
+ InputGestureData.Builder builder = new InputGestureData.Builder();
+ builder.setKeyGestureType(parser.getAttributeInt(null, ATTR_KEY_GESTURE_TYPE));
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ // If the parser has left the initial scope when it was called, break out.
+ if (outerDepth > parser.getDepth()) {
+ throw new RuntimeException(
+ "Parser has left the initial scope of the tag that was being parsed on "
+ + "line number: "
+ + parser.getLineNumber());
+ }
+
+ // If the parser has reached the closing tag for the Input Gesture, break out.
+ if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_INPUT_GESTURE)) {
+ break;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String tag = parser.getName();
+ if (TAG_KEY_TRIGGER.equals(tag)) {
+ builder.setTrigger(InputGestureData.createKeyTrigger(
+ parser.getAttributeInt(null, ATTR_KEY_TRIGGER_KEYCODE),
+ parser.getAttributeInt(null, ATTR_KEY_TRIGGER_MODIFIER_STATE)));
+ } else if (TAG_TOUCHPAD_TRIGGER.equals(tag)) {
+ builder.setTrigger(InputGestureData.createTouchpadTrigger(
+ parser.getAttributeInt(null, ATTR_TOUCHPAD_TRIGGER_GESTURE_TYPE)));
+ } else if (TAG_APP_LAUNCH_DATA.equals(tag)) {
+ final String roleValue = parser.getAttributeValue(null, ATTR_APP_LAUNCH_DATA_ROLE);
+ final String categoryValue = parser.getAttributeValue(null,
+ ATTR_APP_LAUNCH_DATA_CATEGORY);
+ final String classNameValue = parser.getAttributeValue(null,
+ ATTR_APP_LAUNCH_DATA_CLASS_NAME);
+ final String packageNameValue = parser.getAttributeValue(null,
+ ATTR_APP_LAUNCH_DATA_PACKAGE_NAME);
+ final AppLaunchData appLaunchData = AppLaunchData.createLaunchData(categoryValue,
+ roleValue, packageNameValue, classNameValue);
+ if (appLaunchData != null) {
+ builder.setAppLaunchData(appLaunchData);
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ private List<InputGestureData> readInputGestureListFromXml(TypedXmlPullParser parser) throws
+ XmlPullParserException, IOException {
+ List<InputGestureData> inputGestureDataList = new ArrayList<>();
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ // If the parser has left the initial scope when it was called, break out.
+ if (outerDepth > parser.getDepth()) {
+ throw new RuntimeException(
+ "Parser has left the initial scope of the tag that was being parsed on "
+ + "line number: "
+ + parser.getLineNumber());
+ }
+
+ // If the parser has reached the closing tag for the Input Gesture List, break out.
+ if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_INPUT_GESTURE_LIST)) {
+ break;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String tag = parser.getName();
+ if (TAG_INPUT_GESTURE.equals(tag)) {
+ try {
+ inputGestureDataList.add(readInputGestureFromXml(parser));
+ } catch (IllegalArgumentException exception) {
+ Slog.w(TAG, "Invalid parameters for input gesture data: ", exception);
+ continue;
+ }
+ }
+ }
+ return inputGestureDataList;
+ }
+
+ @VisibleForTesting
+ void writeInputGestureXml(OutputStream stream, boolean utf8Encoded,
+ List<InputGestureData> inputGestureDataList) throws IOException {
+ final TypedXmlSerializer serializer;
+ if (utf8Encoded) {
+ serializer = Xml.newFastSerializer();
+ serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+ } else {
+ serializer = Xml.resolveSerializer(stream);
+ }
+
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_ROOT);
+ writeInputGestureListToXml(serializer, inputGestureDataList);
+ serializer.endTag(null, TAG_ROOT);
+ serializer.endDocument();
+ }
+
+ private void writeInputGestureToXml(TypedXmlSerializer serializer,
+ InputGestureData inputGestureData) throws IOException {
+ serializer.startTag(null, TAG_INPUT_GESTURE);
+ serializer.attributeInt(null, ATTR_KEY_GESTURE_TYPE,
+ inputGestureData.getAction().keyGestureType());
+
+ final InputGestureData.Trigger trigger = inputGestureData.getTrigger();
+ if (trigger instanceof InputGestureData.KeyTrigger keyTrigger) {
+ serializer.startTag(null, TAG_KEY_TRIGGER);
+ serializer.attributeInt(null, ATTR_KEY_TRIGGER_KEYCODE, keyTrigger.getKeycode());
+ serializer.attributeInt(null, ATTR_KEY_TRIGGER_MODIFIER_STATE,
+ keyTrigger.getModifierState());
+ serializer.endTag(null, TAG_KEY_TRIGGER);
+ } else if (trigger instanceof InputGestureData.TouchpadTrigger touchpadTrigger) {
+ serializer.startTag(null, TAG_TOUCHPAD_TRIGGER);
+ serializer.attributeInt(null, ATTR_TOUCHPAD_TRIGGER_GESTURE_TYPE,
+ touchpadTrigger.getTouchpadGestureType());
+ serializer.endTag(null, TAG_TOUCHPAD_TRIGGER);
+ }
+
+ if (inputGestureData.getAction().appLaunchData() != null) {
+ serializer.startTag(null, TAG_APP_LAUNCH_DATA);
+ final AppLaunchData appLaunchData = inputGestureData.getAction().appLaunchData();
+ if (appLaunchData instanceof AppLaunchData.RoleData roleData) {
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_ROLE, roleData.getRole());
+ } else if (appLaunchData
+ instanceof AppLaunchData.CategoryData categoryData) {
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_CATEGORY,
+ categoryData.getCategory());
+ } else if (appLaunchData instanceof AppLaunchData.ComponentData componentData) {
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_PACKAGE_NAME,
+ componentData.getPackageName());
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_CLASS_NAME,
+ componentData.getClassName());
+ }
+ serializer.endTag(null, TAG_APP_LAUNCH_DATA);
+ }
+
+ serializer.endTag(null, TAG_INPUT_GESTURE);
+ }
+
+ private void writeInputGestureListToXml(TypedXmlSerializer serializer,
+ List<InputGestureData> inputGestureDataList) throws IOException {
+ serializer.startTag(null, TAG_INPUT_GESTURE_LIST);
+ for (final InputGestureData inputGestureData : inputGestureDataList) {
+ writeInputGestureToXml(serializer, inputGestureData);
+ }
+ serializer.endTag(null, TAG_INPUT_GESTURE_LIST);
+ }
+
+ @VisibleForTesting
+ static class FileInjector {
+ private final SparseArray<AtomicFile> mAtomicFileMap = new SparseArray<>();
+ private final String mFileName;
+
+ FileInjector(String fileName) {
+ mFileName = fileName;
+ }
+
+ InputStream openRead(int userId) throws FileNotFoundException {
+ return getAtomicFileForUserId(userId).openRead();
+ }
+
+ FileOutputStream startWrite(int userId) throws IOException {
+ return getAtomicFileForUserId(userId).startWrite();
+ }
+
+ void finishWrite(int userId, FileOutputStream os, boolean success) {
+ if (success) {
+ getAtomicFileForUserId(userId).finishWrite(os);
+ } else {
+ getAtomicFileForUserId(userId).failWrite(os);
+ }
+ }
+
+ AtomicFile getAtomicFileForUserId(int userId) {
+ if (!mAtomicFileMap.contains(userId)) {
+ mAtomicFileMap.put(userId, new AtomicFile(new File(
+ Environment.buildPath(Environment.getDataSystemDeDirectory(userId),
+ INPUT_MANAGER_DIRECTORY), mFileName)));
+ }
+ return mAtomicFileMap.get(userId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
deleted file mode 100644
index a646d1e9bcb0..000000000000
--- a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.input;
-
-import android.sysprop.InputProperties;
-
-import java.util.Optional;
-
-/**
- * A component of {@link InputManagerService} responsible for managing the input sysprop flags
- *
- * @hide
- */
-@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-public final class InputFeatureFlagProvider {
-
- // To disable Keyboard backlight control via Framework, run:
- // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
- private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
- InputProperties.enable_keyboard_backlight_control().orElse(true);
-
- // To disable Framework controlled keyboard backlight animation run:
- // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
- private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED =
- InputProperties.enable_keyboard_backlight_animation().orElse(false);
-
- // To disable Custom keyboard backlight levels support via IDC files run:
- // adb shell setprop persist.input.keyboard.backlight_custom_levels.enabled false (requires
- // restart)
- private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED =
- InputProperties.enable_keyboard_backlight_custom_levels().orElse(true);
-
- // To disable als based ambient keyboard backlight control run:
- // adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
- // restart)
- private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
- InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
-
- private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
- private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
- private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
- private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
-
- public static boolean isKeyboardBacklightControlEnabled() {
- return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
- }
-
- public static boolean isKeyboardBacklightAnimationEnabled() {
- return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED);
- }
-
- public static boolean isKeyboardBacklightCustomLevelsEnabled() {
- return sKeyboardBacklightCustomLevelsOverride.orElse(
- KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
- }
-
- public static boolean isAmbientKeyboardBacklightControlEnabled() {
- return sAmbientKeyboardBacklightControlOverride.orElse(
- AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
- }
-
- public static void setKeyboardBacklightControlEnabled(boolean enabled) {
- sKeyboardBacklightControlOverride = Optional.of(enabled);
- }
-
- public static void setKeyboardBacklightAnimationEnabled(boolean enabled) {
- sKeyboardBacklightAnimationOverride = Optional.of(enabled);
- }
-
- public static void setKeyboardBacklightCustomLevelsEnabled(boolean enabled) {
- sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
- }
-
- public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
- sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
- }
-
- /**
- * Clears all input feature flag overrides.
- */
- public static void clearOverrides() {
- sKeyboardBacklightControlOverride = Optional.empty();
- sKeyboardBacklightAnimationOverride = Optional.empty();
- sKeyboardBacklightCustomLevelsOverride = Optional.empty();
- sAmbientKeyboardBacklightControlOverride = Optional.empty();
- }
-}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
new file mode 100644
index 000000000000..73d563069632
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static android.hardware.input.InputGestureData.createKeyTrigger;
+
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
+import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
+import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGestureEvent;
+import android.os.SystemProperties;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
+ * gestures and custom gestures defined by other system components using Input APIs.
+ *
+ * TODO(b/365064144): Add implementation to persist data.
+ *
+ */
+final class InputGestureManager {
+ private static final String TAG = "InputGestureManager";
+
+ private static final int KEY_GESTURE_META_MASK =
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+ | KeyEvent.META_META_ON;
+
+ private final Context mContext;
+
+ private static final Object mGestureLock = new Object();
+ @GuardedBy("mGestureLock")
+ private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
+ mCustomInputGestures = new SparseArray<>();
+
+ @GuardedBy("mGestureLock")
+ private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts =
+ new HashMap<>();
+
+ @GuardedBy("mGestureLock")
+ private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of(
+ createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Z,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
+ ));
+
+ public InputGestureManager(Context context) {
+ mContext = context;
+ }
+
+ public void systemRunning() {
+ initSystemShortcuts();
+ blockListBookmarkedTriggers();
+ }
+
+ private void initSystemShortcuts() {
+ // Initialize all system shortcuts
+ List<InputGestureData> systemShortcuts = new ArrayList<>(List.of(
+ createKeyGesture(
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_I,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_L,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_ESCAPE,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_SLASH,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_TAB,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
+ )
+ ));
+ if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT
+ ));
+ }
+ if (enableMoveToNextDisplayShortcut()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_D,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+ ));
+ }
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
+ }
+ if (enableTaskResizingKeyboardShortcuts()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_LEFT_BRACKET,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_RIGHT_BRACKET,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
+ ));
+ }
+ if (keyboardA11yShortcutControl()) {
+ if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_3,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_4,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_5,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_6,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+ ));
+ }
+ }
+ synchronized (mGestureLock) {
+ for (InputGestureData systemShortcut : systemShortcuts) {
+ mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut);
+ }
+ }
+ }
+
+ private void blockListBookmarkedTriggers() {
+ synchronized (mGestureLock) {
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ for (InputGestureData bookmark : im.getAppLaunchBookmarks()) {
+ mBlockListedTriggers.add(bookmark.getTrigger());
+ }
+ }
+ }
+
+ @InputManager.CustomInputGestureResult
+ public int addCustomInputGesture(int userId, InputGestureData newGesture) {
+ synchronized (mGestureLock) {
+ if (mBlockListedTriggers.contains(newGesture.getTrigger())
+ || mSystemShortcuts.containsKey(newGesture.getTrigger())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+ }
+ if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) {
+ if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
+ KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+ }
+ }
+ if (!mCustomInputGestures.contains(userId)) {
+ mCustomInputGestures.put(userId, new HashMap<>());
+ }
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ if (customGestures.containsKey(newGesture.getTrigger())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS;
+ }
+ customGestures.put(newGesture.getTrigger(), newGesture);
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
+ }
+ }
+
+ @InputManager.CustomInputGestureResult
+ public int removeCustomInputGesture(int userId, InputGestureData data) {
+ synchronized (mGestureLock) {
+ if (!mCustomInputGestures.contains(userId)) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
+ }
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ InputGestureData customGesture = customGestures.get(data.getTrigger());
+ if (!Objects.equals(data, customGesture)) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
+ }
+ customGestures.remove(data.getTrigger());
+ if (customGestures.isEmpty()) {
+ mCustomInputGestures.remove(userId);
+ }
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
+ }
+ }
+
+ public void removeAllCustomInputGestures(int userId, @Nullable InputGestureData.Filter filter) {
+ synchronized (mGestureLock) {
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ if (customGestures == null) {
+ return;
+ }
+ if (filter == null) {
+ mCustomInputGestures.remove(userId);
+ return;
+ }
+ customGestures.entrySet().removeIf(entry -> filter.matches(entry.getValue()));
+ if (customGestures.isEmpty()) {
+ mCustomInputGestures.remove(userId);
+ }
+ }
+ }
+
+ @NonNull
+ public List<InputGestureData> getCustomInputGestures(int userId,
+ @Nullable InputGestureData.Filter filter) {
+ synchronized (mGestureLock) {
+ if (!mCustomInputGestures.contains(userId)) {
+ return List.of();
+ }
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ if (filter == null) {
+ return new ArrayList<>(customGestures.values());
+ }
+ List<InputGestureData> result = new ArrayList<>();
+ for (InputGestureData customGesture : customGestures.values()) {
+ if (filter.matches(customGesture)) {
+ result.add(customGesture);
+ }
+ }
+ return result;
+ }
+ }
+
+ @Nullable
+ public InputGestureData getCustomGestureForKeyEvent(@UserIdInt int userId, KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ synchronized (mGestureLock) {
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ if (customGestures == null) {
+ return null;
+ }
+ int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+ return customGestures.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+ }
+ }
+
+ @Nullable
+ public InputGestureData getCustomGestureForTouchpadGesture(@UserIdInt int userId,
+ int touchpadGestureType) {
+ if (touchpadGestureType == InputGestureData.TOUCHPAD_GESTURE_TYPE_UNKNOWN) {
+ return null;
+ }
+ synchronized (mGestureLock) {
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ if (customGestures == null) {
+ return null;
+ }
+ return customGestures.get(InputGestureData.createTouchpadTrigger(touchpadGestureType));
+ }
+ }
+
+ @Nullable
+ public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ synchronized (mGestureLock) {
+ int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+ return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+ }
+ }
+
+ private static InputGestureData createKeyGesture(int keycode, int modifierState,
+ int keyGestureType) {
+ return new InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keycode, modifierState))
+ .setKeyGestureType(keyGestureType)
+ .build();
+ }
+
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("InputGestureManager:");
+ ipw.increaseIndent();
+ synchronized (mGestureLock) {
+ ipw.println("System Shortcuts:");
+ ipw.increaseIndent();
+ for (InputGestureData systemShortcut : mSystemShortcuts.values()) {
+ ipw.println(systemShortcut);
+ }
+ ipw.decreaseIndent();
+ ipw.println("Blocklisted Triggers:");
+ ipw.increaseIndent();
+ for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) {
+ ipw.println(blocklistedTrigger);
+ }
+ ipw.decreaseIndent();
+ ipw.println("Custom Gestures:");
+ ipw.increaseIndent();
+ int size = mCustomInputGestures.size();
+ for (int i = 0; i < size; i++) {
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.valueAt(i);
+ ipw.println("UserId = " + mCustomInputGestures.keyAt(i));
+ ipw.increaseIndent();
+ for (InputGestureData customGesture : customGestures.values()) {
+ ipw.println(customGesture);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 92812670057a..bc44fed21f2d 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -20,13 +20,17 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.graphics.PointF;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.KeyGestureEvent;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseBooleanArray;
import android.view.InputChannel;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
+import com.android.internal.policy.IShortcutService;
import java.util.List;
@@ -44,10 +48,18 @@ public abstract class InputManagerInternal {
public abstract void setDisplayViewports(List<DisplayViewport> viewports);
/**
+ * Called by {@link com.android.server.display.DisplayManagerService} to inform InputManager
+ * about changes in the displays topology.
+ */
+ public abstract void setDisplayTopology(DisplayTopology topology);
+
+ /**
* Called by the power manager to tell the input manager whether it should start
- * watching for wake events.
+ * watching for wake events on given displays.
+ *
+ * @param displayInteractivities Map of display ids to their current interactive state.
*/
- public abstract void setInteractive(boolean interactive);
+ public abstract void setDisplayInteractivities(SparseBooleanArray displayInteractivities);
/**
* Toggles Caps Lock state for input device with specific id.
@@ -124,6 +136,13 @@ public abstract class InputManagerInternal {
*/
public abstract void notifyInputMethodConnectionActive(boolean connectionIsActive);
+ /**
+ * Notify user id changes to input.
+ *
+ * TODO(b/362473586): Cleanup after input shifts to Lifecycle with user change callbacks
+ */
+ public abstract void setCurrentUser(@UserIdInt int newUserId);
+
/** Callback interface for notifications relating to the lid switch. */
public interface LidSwitchCallback {
/**
@@ -259,4 +278,35 @@ public abstract class InputManagerInternal {
*/
public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes,
int modifierState, @KeyGestureEvent.KeyGestureType int event);
+
+ /**
+ * Sets the magnification scale factor for pointer icons.
+ *
+ * @param displayId the ID of the display where the new scale factor is applied.
+ * @param scaleFactor the new scale factor to be applied for pointer icons.
+ */
+ public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
+
+
+ /**
+ * Register shortcuts for input manager to dispatch.
+ * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+ * @hide
+ */
+ public abstract void registerShortcutKey(long shortcutCode,
+ IShortcutService shortcutKeyReceiver) throws RemoteException;
+
+ /**
+ * Set whether the given input device can wake up the kernel from sleep
+ * when it generates input events. By default, usually only internal (built-in)
+ * input devices can wake the kernel from sleep. For an external input device
+ * that supports remote wakeup to be able to wake the kernel, this must be called
+ * after each time the device is connected/added.
+ *
+ * @param deviceId the device ID of the input device.
+ * @param enabled When true, device will be configured to wake up kernel.
+ *
+ * @return true if setting power wakeup was successful.
+ */
+ public abstract boolean setKernelWakeEnabled(int deviceId, boolean enabled);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fd7479eb8d48..aee5e7f52c5f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -19,12 +19,14 @@ package com.android.server.input;
import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static android.content.PermissionChecker.PERMISSION_GRANTED;
import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
import android.Manifest;
import android.annotation.EnforcePermission;
@@ -49,7 +51,9 @@ import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors;
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
+import android.hardware.input.AidlInputGestureData;
import android.hardware.input.HostUsiVersion;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
@@ -62,6 +66,7 @@ import android.hardware.input.IKeyboardBacklightListener;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
@@ -95,6 +100,7 @@ import android.os.vibrator.VibrationEffectSegment;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
@@ -117,6 +123,7 @@ import android.view.SurfaceControl;
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -125,11 +132,13 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerInternal.LidSwitchCallback;
import com.android.server.input.debug.FocusEventDebugView;
@@ -172,6 +181,8 @@ public class InputManagerService extends IInputManager.Stub
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+ private static final int MSG_CURRENT_USER_CHANGED = 4;
+ private static final int MSG_SYSTEM_READY = 5;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
private static final AdditionalDisplayInputProperties
@@ -181,10 +192,11 @@ public class InputManagerService extends IInputManager.Stub
private final Context mContext;
private final InputManagerHandler mHandler;
+ @UserIdInt
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
private DisplayManagerInternal mDisplayManagerInternal;
private WindowManagerInternal mWindowManagerInternal;
- private PackageManagerInternal mPackageManagerInternal;
private final File mDoubleTouchGestureEnableFile;
@@ -325,6 +337,9 @@ public class InputManagerService extends IInputManager.Stub
// Manages Sticky modifier state
private final StickyModifierStateController mStickyModifierStateController;
private final KeyGestureController mKeyGestureController;
+ /** Fallback actions by key code */
+ private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
+ new SparseArray<>();
// Manages Keyboard microphone mute led
private final KeyboardLedController mKeyboardLedController;
@@ -338,6 +353,9 @@ public class InputManagerService extends IInputManager.Stub
// Manages loading PointerIcons
private final PointerIconCache mPointerIconCache;
+ // Manages storage and retrieval of input data.
+ private final InputDataStore mInputDataStore;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -458,11 +476,9 @@ public class InputManagerService extends IInputManager.Stub
}
KeyboardBacklightControllerInterface getKeyboardBacklightController(
- NativeInputManagerService nativeService, PersistentDataStore dataStore) {
- return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
- ? new KeyboardBacklightController(mContext, nativeService, dataStore,
- mLooper, mUEventManager)
- : new KeyboardBacklightControllerInterface() {};
+ NativeInputManagerService nativeService) {
+ return new KeyboardBacklightController(mContext, nativeService, mLooper,
+ mUEventManager);
}
}
@@ -487,9 +503,11 @@ public class InputManagerService extends IInputManager.Stub
injector.getLooper(), this) : null;
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
injector.getUEventManager());
- mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore);
+ mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative);
mStickyModifierStateController = new StickyModifierStateController();
- mKeyGestureController = new KeyGestureController(mContext, injector.getLooper());
+ mInputDataStore = new InputDataStore();
+ mKeyGestureController = new KeyGestureController(mContext, injector.getLooper(),
+ mInputDataStore);
mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
mNative);
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -553,6 +571,14 @@ public class InputManagerService extends IInputManager.Stub
Watchdog.getInstance().addMonitor(this);
}
+ private void onBootPhase(int phase) {
+ // On ActivityManager thread, shift to handler to avoid blocking other system services in
+ // this boot phase.
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
+ }
+ }
+
// TODO(BT) Pass in parameter for bluetooth system
public void systemRunning() {
if (DEBUG) {
@@ -561,7 +587,6 @@ public class InputManagerService extends IInputManager.Stub
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mSettingsObserver.registerAndUpdate();
@@ -611,8 +636,10 @@ public class InputManagerService extends IInputManager.Stub
mKeyRemapper.systemRunning();
mPointerIconCache.systemRunning();
mKeyboardGlyphManager.systemRunning();
-
- initKeyGestures();
+ if (useKeyGestureEventHandler()) {
+ mKeyGestureController.systemRunning();
+ initKeyGestures();
+ }
}
private void reloadDeviceAliases() {
@@ -634,6 +661,10 @@ public class InputManagerService extends IInputManager.Stub
mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId());
}
+ private void setDisplayTopologyInternal(DisplayTopology topology) {
+ mNative.setDisplayTopology(topology.getGraph());
+ }
+
/**
* Gets the current state of a key or button by key code.
* @param deviceId The input device id, or -1 to consult all devices.
@@ -2301,6 +2332,13 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
+ private void notifyTouchpadThreeFingerTap() {
+ mKeyGestureController.handleTouchpadGesture(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP);
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
if (DEBUG) {
Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
@@ -2469,6 +2507,14 @@ public class InputManagerService extends IInputManager.Stub
mFocusEventDebugView.reportKeyEvent(event);
}
}
+ if (useKeyGestureEventHandler() && mKeyGestureController.interceptKeyBeforeQueueing(event,
+ policyFlags)) {
+ // If key gesture gets triggered, we send the event to policy with KEY_GESTURE flag
+ // indicating, the event is used in triggering a key gesture. We can't block event
+ // like Power or volume keys since policy might still want to handle it to change
+ // certain states.
+ policyFlags |= WindowManagerPolicyConstants.FLAG_KEY_GESTURE_TRIGGERED;
+ }
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}
@@ -2511,10 +2557,75 @@ public class InputManagerService extends IInputManager.Stub
null, null, null) == PERMISSION_GRANTED;
}
- private void initKeyGestures() {
- if (!useKeyGestureEventHandler()) {
- return;
+ // Native callback.
+ @SuppressWarnings("unused")
+ private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
+ if (interceptUnhandledKey(event, focus)) {
+ return null;
+ }
+ // TODO(b/358569822): Move fallback logic to KeyGestureController
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+ return null;
+ }
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+ final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0;
+
+ // Check for fallback actions specified by the key character map.
+ final KeyCharacterMap.FallbackAction fallbackAction;
+ if (initialDown) {
+ fallbackAction = kcm.getFallbackAction(keyCode, metaState);
+ } else {
+ fallbackAction = mFallbackActions.get(keyCode);
+ }
+
+ if (fallbackAction == null) {
+ return null;
+ }
+ KeyEvent fallbackEvent = null;
+ final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+ fallbackEvent = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), fallbackAction.keyCode,
+ event.getRepeatCount(), fallbackAction.metaState,
+ event.getDeviceId(), event.getScanCode(),
+ flags, event.getSource(), event.getDisplayId(), null);
+
+ if (!interceptFallback(focus, fallbackEvent, policyFlags)) {
+ fallbackEvent.recycle();
+ fallbackEvent = null;
}
+
+ if (initialDown) {
+ mFallbackActions.put(keyCode, fallbackAction);
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ mFallbackActions.remove(keyCode);
+ fallbackAction.recycle();
+ }
+ return fallbackEvent;
+ }
+
+ private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
+ int policyFlags) {
+ int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
+ if ((actions & ACTION_PASS_TO_USER) == 0) {
+ return false;
+ }
+ long delayMillis = interceptKeyBeforeDispatching(focusedToken, fallbackEvent, policyFlags);
+ return delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken);
+ }
+
+ private boolean interceptUnhandledKey(KeyEvent event, IBinder focus) {
+ if (useKeyGestureEventHandler() && mKeyGestureController.interceptUnhandledKey(event,
+ focus)) {
+ return true;
+ }
+ return mWindowManagerCallbacks.interceptUnhandledKey(event, focus);
+ }
+
+ private void initKeyGestures() {
InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
@Override
@@ -2566,12 +2677,6 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
- private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
- return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private void onPointerDownOutsideFocus(IBinder touchedToken) {
mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
}
@@ -2617,6 +2722,9 @@ public class InputManagerService extends IInputManager.Stub
@SuppressWarnings("unused")
private void notifyStylusGestureStarted(int deviceId, long eventTime) {
mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+ if (mDisplayManagerInternal != null) {
+ mDisplayManagerInternal.stylusGestureStarted(eventTime);
+ }
}
/**
@@ -2846,10 +2954,11 @@ public class InputManagerService extends IInputManager.Stub
private void enforceManageKeyGesturePermission() {
// TODO(b/361567988): Use @EnforcePermission to enforce permission once flag guarding the
// permission is rolled out
- if (mSystemReady) {
- String systemUIPackage = mContext.getString(R.string.config_systemUi);
- int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
- .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
+ String systemUIPackage = mContext.getString(R.string.config_systemUi);
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ if (pm != null) {
+ int systemUIAppId = UserHandle.getAppId(
+ pm.getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
UserHandle.USER_SYSTEM));
if (UserHandle.getCallingAppId() == systemUIAppId) {
return;
@@ -2902,6 +3011,63 @@ public class InputManagerService extends IInputManager.Stub
mKeyGestureController.unregisterKeyGestureHandler(handler, Binder.getCallingPid());
}
+ @Override
+ @PermissionManuallyEnforced
+ public int addCustomInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData inputGestureData) {
+ enforceManageKeyGesturePermission();
+
+ Objects.requireNonNull(inputGestureData);
+ return mKeyGestureController.addCustomInputGesture(userId, inputGestureData);
+ }
+
+ @Override
+ @PermissionManuallyEnforced
+ public int removeCustomInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData inputGestureData) {
+ enforceManageKeyGesturePermission();
+
+ Objects.requireNonNull(inputGestureData);
+ return mKeyGestureController.removeCustomInputGesture(userId, inputGestureData);
+ }
+
+ @Override
+ @PermissionManuallyEnforced
+ public void removeAllCustomInputGestures(@UserIdInt int userId, int tag) {
+ enforceManageKeyGesturePermission();
+
+ mKeyGestureController.removeAllCustomInputGestures(userId, InputGestureData.Filter.of(tag));
+ }
+
+ @Override
+ public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId, int tag) {
+ return mKeyGestureController.getCustomInputGestures(userId,
+ InputGestureData.Filter.of(tag));
+ }
+
+ @Override
+ public AidlInputGestureData[] getAppLaunchBookmarks() {
+ return mKeyGestureController.getAppLaunchBookmarks();
+ }
+
+ @Override
+ public void resetLockedModifierState() {
+ mNative.resetLockedModifierState();
+ }
+
+ private void onUserSwitching(@NonNull SystemService.TargetUser from,
+ @NonNull SystemService.TargetUser to) {
+ if (DEBUG) {
+ Slog.d(TAG, "onUserSwitching from=" + from + " to=" + to);
+ }
+ mHandler.obtainMessage(MSG_CURRENT_USER_CHANGED, to.getUserIdentifier()).sendToTarget();
+ }
+
+ private void handleCurrentUserChanged(@UserIdInt int userId) {
+ mCurrentUserId = userId;
+ mKeyGestureController.setCurrentUserId(userId);
+ }
+
/**
* Callback interface implemented by the Window Manager.
*/
@@ -2986,9 +3152,9 @@ public class InputManagerService extends IInputManager.Stub
long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags);
/**
- * Dispatch unhandled key
+ * Intercept unhandled key
*/
- KeyEvent dispatchUnhandledKey(IBinder token, KeyEvent event, int policyFlags);
+ boolean interceptUnhandledKey(KeyEvent event, IBinder token);
int getPointerLayer();
@@ -3070,6 +3236,12 @@ public class InputManagerService extends IInputManager.Stub
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ case MSG_CURRENT_USER_CHANGED:
+ handleCurrentUserChanged((int) msg.obj);
+ break;
+ case MSG_SYSTEM_READY:
+ systemRunning();
+ break;
}
}
}
@@ -3254,6 +3426,39 @@ public class InputManagerService extends IInputManager.Stub
}
}
+ /**
+ * {@link SystemService} used to publish and manage the lifecycle of {@link InputManagerService}
+ */
+ public static final class Lifecycle extends SystemService {
+
+ private final InputManagerService mService;
+
+ public Lifecycle(@NonNull Context context) {
+ super(context);
+ mService = new InputManagerService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.INPUT_SERVICE, mService,
+ /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ mService.onBootPhase(phase);
+ }
+
+ @Override
+ public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+ mService.onUserSwitching(from, to);
+ }
+
+ public InputManagerService getService() {
+ return mService;
+ }
+ }
+
private final class LocalService extends InputManagerInternal {
@Override
public void setDisplayViewports(List<DisplayViewport> viewports) {
@@ -3261,10 +3466,27 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void setInteractive(boolean interactive) {
- mNative.setInteractive(interactive);
- mBatteryController.onInteractiveChanged(interactive);
- mKeyboardBacklightController.onInteractiveChanged(interactive);
+ public void setDisplayTopology(DisplayTopology topology) {
+ setDisplayTopologyInternal(topology);
+ }
+
+ @Override
+ public void setDisplayInteractivities(SparseBooleanArray displayInteractivities) {
+ boolean globallyInteractive = false;
+ ArraySet<Integer> nonInteractiveDisplays = new ArraySet<>();
+ for (int i = 0; i < displayInteractivities.size(); i++) {
+ final int displayId = displayInteractivities.keyAt(i);
+ final boolean displayInteractive = displayInteractivities.get(displayId);
+ if (displayInteractive) {
+ globallyInteractive = true;
+ } else {
+ nonInteractiveDisplays.add(displayId);
+ }
+ }
+ mNative.setNonInteractiveDisplays(
+ nonInteractiveDisplays.stream().mapToInt(Integer::intValue).toArray());
+ mBatteryController.onInteractiveChanged(globallyInteractive);
+ mKeyboardBacklightController.onInteractiveChanged(globallyInteractive);
}
// TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
@@ -3414,6 +3636,27 @@ public class InputManagerService extends IInputManager.Stub
int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) {
mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType);
}
+
+ @Override
+ public void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
+ InputManagerService.this.setAccessibilityPointerIconScaleFactor(displayId, scaleFactor);
+ }
+
+ @Override
+ public void setCurrentUser(@UserIdInt int newUserId) {
+ mHandler.obtainMessage(MSG_CURRENT_USER_CHANGED, newUserId).sendToTarget();
+ }
+
+ @Override
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException {
+ mKeyGestureController.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+ }
+
+ @Override
+ public boolean setKernelWakeEnabled(int deviceId, boolean enabled) {
+ return mNative.setKernelWakeEnabled(deviceId, enabled);
+ }
}
@Override
@@ -3596,6 +3839,10 @@ public class InputManagerService extends IInputManager.Stub
mPointerIconCache.setPointerScale(scale);
}
+ void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
+ mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
+ }
+
interface KeyboardBacklightControllerInterface {
default void incrementKeyboardBacklight(int deviceId) {}
default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d70bd8b17ddf..bf08563db30d 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -63,6 +63,12 @@ class InputSettingsObserver extends ContentObserver {
mObservers = Map.ofEntries(
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
(reason) -> updateMousePointerSpeed()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING),
+ (reason) -> updateMouseReverseVerticalScrolling()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON),
+ (reason) -> updateMouseSwapPrimaryButton()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
(reason) -> updateTouchpadPointerSpeed()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
@@ -75,6 +81,8 @@ class InputSettingsObserver extends ContentObserver {
(reason) -> updateTouchpadHardwareStateNotificationsEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
(reason) -> updateTouchpadRightClickZoneEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
+ (reason) -> updateTouchpadSystemGesturesEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
(reason) -> updateShowTouches()),
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -111,7 +119,10 @@ class InputSettingsObserver extends ContentObserver {
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_STROKE_STYLE),
(reason) -> updatePointerStrokeStyleFromSettings()),
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SCALE),
- (reason) -> updatePointerScaleFromSettings()));
+ (reason) -> updatePointerScaleFromSettings()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.TOUCHPAD_THREE_FINGER_TAP_CUSTOMIZATION),
+ (reason) -> updateTouchpadThreeFingerTapShortcutEnabled()));
}
/**
@@ -163,6 +174,16 @@ class InputSettingsObserver extends ContentObserver {
mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
}
+ private void updateMouseReverseVerticalScrolling() {
+ mNative.setMouseReverseVerticalScrollingEnabled(
+ InputSettings.isMouseReverseVerticalScrollingEnabled(mContext));
+ }
+
+ private void updateMouseSwapPrimaryButton() {
+ mNative.setMouseSwapPrimaryButtonEnabled(
+ InputSettings.isMouseSwapPrimaryButtonEnabled(mContext));
+ }
+
private void updateTouchpadPointerSpeed() {
mNative.setTouchpadPointerSpeed(
constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
@@ -189,6 +210,15 @@ class InputSettingsObserver extends ContentObserver {
mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
}
+ private void updateTouchpadThreeFingerTapShortcutEnabled() {
+ mNative.setTouchpadThreeFingerTapShortcutEnabled(
+ InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
+ }
+
+ private void updateTouchpadSystemGesturesEnabled() {
+ mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index d8cf68e1b87c..5bc08a0ff85d 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -89,6 +89,9 @@ public class InputShellCommand extends ShellCommand {
private static final int DEFAULT_FLAGS = 0;
private static final boolean INJECT_ASYNC = true;
private static final boolean INJECT_SYNC = false;
+ private static final long SECOND_IN_MILLISECONDS = 1000;
+
+ public static final int SWIPE_EVENT_HZ_DEFAULT = 120;
/** Modifier key to meta state */
private static final Map<Integer, Integer> MODIFIER;
@@ -520,11 +523,30 @@ public class InputShellCommand extends ShellCommand {
}
long now = SystemClock.uptimeMillis();
final long endTime = down + duration;
+ final float swipeEventPeriodMillis =
+ (float) SECOND_IN_MILLISECONDS / SWIPE_EVENT_HZ_DEFAULT;
+ int injected = 1;
while (now < endTime) {
- final long elapsedTime = now - down;
+ // Ensure that we inject at most at the frequency of SWIPE_EVENT_HZ_DEFAULT
+ // by waiting an additional delta between the actual time and expected time.
+ long elapsedTime = now - down;
+ final long errorMillis =
+ (long) Math.floor(injected * swipeEventPeriodMillis - elapsedTime);
+ if (errorMillis > 0) {
+ // Make sure not to exceed the duration and inject an extra event.
+ if (errorMillis > endTime - now) {
+ sleep(endTime - now);
+ break;
+ }
+ sleep(errorMillis);
+ }
+
+ now = SystemClock.uptimeMillis();
+ elapsedTime = now - down;
final float alpha = (float) elapsedTime / duration;
injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, down, now,
lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f, displayId);
+ injected++;
now = SystemClock.uptimeMillis();
}
injectMotionEvent(inputSource, MotionEvent.ACTION_UP, down, now, x2, y2, 0.0f,
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 4538b49b73c5..99c01ce5c15a 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -16,18 +16,31 @@
package com.android.server.input;
-import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
+
+import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import android.annotation.BinderThread;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.input.AidlInputGestureData;
import android.hardware.input.AidlKeyGestureEvent;
+import android.hardware.input.AppLaunchData;
import android.hardware.input.IKeyGestureEventListener;
import android.hardware.input.IKeyGestureHandler;
+import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
import android.hardware.input.KeyGestureEvent;
import android.os.Handler;
import android.os.IBinder;
@@ -35,21 +48,27 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemProperties;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.InputDevice;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.IShortcutService;
+import com.android.server.policy.KeyCombinationManager;
import java.util.ArrayDeque;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
@@ -68,8 +87,13 @@ final class KeyGestureController {
// Maximum key gesture events that are tracked and will be available in input dump.
private static final int MAX_TRACKED_EVENTS = 10;
+ private static final int SHORTCUT_META_MASK =
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON
+ | KeyEvent.META_SHIFT_ON;
private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1;
+ private static final int MSG_PERSIST_CUSTOM_GESTURES = 2;
+ private static final int MSG_LOAD_CUSTOM_GESTURES = 3;
// must match: config_settingsKeyBehavior in config.xml
private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0;
@@ -82,20 +106,43 @@ final class KeyGestureController {
private static final int SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1;
private static final int LAST_SEARCH_KEY_BEHAVIOR = SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY;
+ // must match: config_keyChordPowerVolumeUp in config.xml
+ static final int POWER_VOLUME_UP_BEHAVIOR_NOTHING = 0;
+ static final int POWER_VOLUME_UP_BEHAVIOR_MUTE = 1;
+ static final int POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS = 2;
+
private final Context mContext;
private final Handler mHandler;
private final int mSystemPid;
+ private final KeyCombinationManager mKeyCombinationManager;
+ private final SettingsObserver mSettingsObserver;
+ private final AppLaunchShortcutManager mAppLaunchShortcutManager;
+ private final InputGestureManager mInputGestureManager;
+ @GuardedBy("mInputDataStore")
+ private final InputDataStore mInputDataStore;
+ private static final Object mUserLock = new Object();
+ @UserIdInt
+ @GuardedBy("mUserLock")
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
// Pending actions
private boolean mPendingMetaAction;
private boolean mPendingCapsLockToggle;
private boolean mPendingHideRecentSwitcher;
+ // Platform behaviors
+ private boolean mHasFeatureWatch;
+ private boolean mHasFeatureLeanback;
+
// Key behaviors
- private boolean mEnableBugReportKeyboardShortcut;
private int mSearchKeyBehavior;
private int mSettingsKeyBehavior;
+ // Settings behaviors
+ private int mRingerToggleChord = Settings.Secure.VOLUME_HUSH_OFF;
+ private int mPowerVolUpBehavior;
+
+
// List of currently registered key gesture event listeners keyed by process pid
@GuardedBy("mKeyGestureEventListenerRecords")
private final SparseArray<KeyGestureEventListenerRecord>
@@ -112,7 +159,7 @@ final class KeyGestureController {
/** Currently fully consumed key codes per device */
private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
- KeyGestureController(Context context, Looper looper) {
+ KeyGestureController(Context context, Looper looper, InputDataStore inputDataStore) {
mContext = context;
mHandler = new Handler(looper, this::handleMessage);
mSystemPid = Process.myPid();
@@ -128,11 +175,19 @@ final class KeyGestureController {
return Integer.compare(p1, p2);
}
});
+ mKeyCombinationManager = new KeyCombinationManager(mHandler);
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
+ mInputGestureManager = new InputGestureManager(mContext);
+ mInputDataStore = inputDataStore;
initBehaviors();
+ initKeyCombinationRules();
}
private void initBehaviors() {
- mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable"));
+ PackageManager pm = mContext.getPackageManager();
+ mHasFeatureWatch = pm.hasSystemFeature(FEATURE_WATCH);
+ mHasFeatureLeanback = pm.hasSystemFeature(FEATURE_LEANBACK);
Resources res = mContext.getResources();
mSearchKeyBehavior = res.getInteger(R.integer.config_searchKeyBehavior);
@@ -145,6 +200,261 @@ final class KeyGestureController {
|| mSettingsKeyBehavior > LAST_SETTINGS_KEY_BEHAVIOR) {
mSettingsKeyBehavior = SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY;
}
+
+ mHandler.post(this::initBehaviorsFromSettings);
+ }
+
+ private void initBehaviorsFromSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ mRingerToggleChord = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF,
+ UserHandle.USER_CURRENT);
+
+ mPowerVolUpBehavior = Settings.Global.getInt(resolver,
+ Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_keyChordPowerVolumeUp));
+ }
+
+ private void initKeyCombinationRules() {
+ if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
+ return;
+ }
+ // TODO(b/358569822): Handle Power, Back key properly since key combination gesture is
+ // captured here and rest of the Power, Back key behaviors are handled in PWM
+ final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableScreenshotChord);
+
+ if (screenshotChordEnabled) {
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_POWER) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+
+ @Override
+ public void cancel() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ });
+
+ if (mHasFeatureWatch) {
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_POWER,
+ KeyEvent.KEYCODE_STEM_PRIMARY) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleMultiKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+ KeyEvent.KEYCODE_STEM_PRIMARY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+ @Override
+ public void cancel() {
+ handleMultiKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+ KeyEvent.KEYCODE_STEM_PRIMARY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ });
+ }
+ }
+
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_UP) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+
+ @Override
+ public void cancel() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ });
+
+ // Volume up + power can either be the "ringer toggle chord" or as another way to
+ // launch GlobalActions. This behavior can change at runtime so we must check behavior
+ // inside the TwoKeysCombinationRule.
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_VOLUME_UP,
+ KeyEvent.KEYCODE_POWER) {
+ @Override
+ public boolean preCondition() {
+ if (!isKeyGestureSupported(getGestureType())) {
+ return false;
+ }
+ switch (mPowerVolUpBehavior) {
+ case POWER_VOLUME_UP_BEHAVIOR_MUTE:
+ return mRingerToggleChord != Settings.Secure.VOLUME_HUSH_OFF;
+ default:
+ return true;
+ }
+ }
+ @Override
+ public void execute() {
+ int gestureType = getGestureType();
+ if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
+ return;
+ }
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
+ gestureType, KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+ @Override
+ public void cancel() {
+ int gestureType = getGestureType();
+ if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
+ return;
+ }
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
+ gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+
+ @KeyGestureEvent.KeyGestureType
+ private int getGestureType() {
+ switch (mPowerVolUpBehavior) {
+ case POWER_VOLUME_UP_BEHAVIOR_MUTE -> {
+ return KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD;
+ }
+ case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS -> {
+ return KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS;
+ }
+ default -> {
+ return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
+ }
+ }
+ }
+ });
+
+ if (mHasFeatureLeanback) {
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_DPAD_DOWN) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+
+ @Override
+ public void cancel() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ @Override
+ public long getKeyInterceptDelayMs() {
+ // Use a timeout of 0 to prevent additional latency in processing of
+ // this key. This will potentially cause some unwanted UI actions if the
+ // user does end up triggering the key combination later, but in most
+ // cases, the user will simply hit a single key, and this will allow us
+ // to process it without first waiting to see if the combination is
+ // going to be triggered.
+ return 0;
+ }
+ });
+
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_DPAD_CENTER) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT);
+ }
+
+ @Override
+ public void execute() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+ @Override
+ public void cancel() {
+ handleMultiKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ @Override
+ public long getKeyInterceptDelayMs() {
+ return 0;
+ }
+ });
+ }
+ }
+
+ public void systemRunning() {
+ mSettingsObserver.observe();
+ mAppLaunchShortcutManager.systemRunning();
+ mInputGestureManager.systemRunning();
+
+ int userId;
+ synchronized (mUserLock) {
+ userId = mCurrentUserId;
+ }
+ // Load the system user's input gestures.
+ mHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+
+ public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+ final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
+ if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
+ && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ return mKeyCombinationManager.interceptKey(event, interactive);
+ }
+ return false;
}
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
@@ -153,9 +463,25 @@ final class KeyGestureController {
// KeyGestureHandler (PWM is one of the handlers)
final int keyCode = event.getKeyCode();
final int deviceId = event.getDeviceId();
+ final int flags = event.getFlags();
final long keyConsumed = -1;
final long keyNotConsumed = 0;
+ if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
+ if (mKeyCombinationManager.isKeyConsumed(event)) {
+ return keyConsumed;
+ }
+
+ if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ final long now = SystemClock.uptimeMillis();
+ final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(
+ keyCode);
+ if (now < interceptTimeout) {
+ return interceptTimeout - now;
+ }
+ }
+ }
+
Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
if (consumedKeys == null) {
consumedKeys = new HashSet<>();
@@ -183,7 +509,7 @@ final class KeyGestureController {
private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
- final int metaState = event.getMetaState();
+ final int metaState = event.getMetaState() & SHORTCUT_META_MASK;
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int displayId = event.getDisplayId();
@@ -200,21 +526,40 @@ final class KeyGestureController {
mPendingCapsLockToggle = false;
}
+ // Handle App launch shortcuts
+ AppLaunchShortcutManager.InterceptKeyResult result = mAppLaunchShortcutManager.interceptKey(
+ event);
+ if (result.consumed()) {
+ return true;
+ }
+ if (result.appLaunchData() != null) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, result.appLaunchData());
+ }
+
+ // Handle system shortcuts
+ if (firstDown) {
+ InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent(
+ event);
+ if (systemShortcut != null) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ systemShortcut.getAction().keyGestureType(),
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ displayId, focusedToken, /* flags = */0,
+ systemShortcut.getAction().appLaunchData());
+ }
+ }
+
+ // Handle system keys
switch (keyCode) {
- case KeyEvent.KEYCODE_A:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
case KeyEvent.KEYCODE_RECENT_APPS:
if (firstDown) {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_APP_SWITCH:
@@ -222,151 +567,15 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
KeyGestureEvent.ACTION_GESTURE_START, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
} else if (!down) {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0);
+ focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0,
+ /* appLaunchData = */null);
}
return true;
- case KeyEvent.KEYCODE_H:
- case KeyEvent.KEYCODE_ENTER:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_I:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_L:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_N:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- } else {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- }
- break;
- case KeyEvent.KEYCODE_S:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_DEL:
- if (newBugreportKeyboardShortcut()) {
- if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed()
- && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- }
- // fall through
- case KeyEvent.KEYCODE_ESCAPE:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- } else if (event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- } else {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- } else if (event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- }
- break;
- case KeyEvent.KEYCODE_SLASH:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- }
- break;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -375,7 +584,7 @@ final class KeyGestureController {
? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP
: KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
@@ -383,7 +592,7 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
@@ -391,7 +600,7 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
@@ -400,7 +609,7 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_ALL_APPS:
@@ -408,7 +617,7 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_NOTIFICATION:
@@ -416,7 +625,7 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_SEARCH:
@@ -424,7 +633,7 @@ final class KeyGestureController {
return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
break;
@@ -435,13 +644,13 @@ final class KeyGestureController {
new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
} else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
handleKeyGesture(deviceId,
new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
}
return true;
@@ -451,7 +660,7 @@ final class KeyGestureController {
event.isShiftPressed() ? KeyEvent.META_SHIFT_ON : 0,
KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_CAPS_LOCK:
@@ -461,7 +670,8 @@ final class KeyGestureController {
AidlKeyGestureEvent eventToNotify = createKeyGestureEvent(deviceId,
new int[]{keyCode}, metaState,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0);
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0,
+ /* appLaunchData = */null);
Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT,
eventToNotify);
mHandler.sendMessage(msg);
@@ -472,7 +682,7 @@ final class KeyGestureController {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
return true;
case KeyEvent.KEYCODE_META_LEFT:
@@ -493,7 +703,7 @@ final class KeyGestureController {
KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
} else if (mPendingMetaAction) {
mPendingMetaAction = false;
@@ -502,19 +712,14 @@ final class KeyGestureController {
/* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
}
}
return true;
case KeyEvent.KEYCODE_TAB:
if (firstDown) {
- if (event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
- } else if (!mPendingHideRecentSwitcher) {
+ if (!mPendingHideRecentSwitcher) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
@@ -524,7 +729,7 @@ final class KeyGestureController {
KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
KeyGestureEvent.ACTION_GESTURE_START, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
}
}
@@ -545,7 +750,7 @@ final class KeyGestureController {
KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
// Toggle Caps Lock on META-ALT.
@@ -555,10 +760,32 @@ final class KeyGestureController {
KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0);
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
}
}
break;
+ case KeyEvent.KEYCODE_LOCK:
+ if (enableNew25q2Keycodes()) {
+ if (firstDown) {
+ handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_LOCK},
+ /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+ /* flags = */0, /* appLaunchData = */null);
+ }
+ }
+ return true;
+ case KeyEvent.KEYCODE_FULLSCREEN:
+ if (enableNew25q2Keycodes()) {
+ if (firstDown) {
+ handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_FULLSCREEN},
+ /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+ /* flags = */0, /* appLaunchData = */null);
+ }
+ }
+ return true;
case KeyEvent.KEYCODE_ASSIST:
Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
return true;
@@ -573,16 +800,110 @@ final class KeyGestureController {
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
return true;
+ case KeyEvent.KEYCODE_DO_NOT_DISTURB:
+ // TODO(b/365920375): Implement 25Q2 keycode implementation in system
+ return true;
+ }
+
+ // Handle shortcuts through shortcut services
+ if (mAppLaunchShortcutManager.handleShortcutService(event)) {
+ return true;
+ }
+
+ // Handle custom shortcuts
+ if (firstDown) {
+ InputGestureData customGesture;
+ synchronized (mUserLock) {
+ customGesture = mInputGestureManager.getCustomGestureForKeyEvent(mCurrentUserId,
+ event);
+ }
+ if (customGesture == null) {
+ return false;
+ }
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ customGesture.getAction().keyGestureType(),
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ displayId, focusedToken, /* flags = */0,
+ customGesture.getAction().appLaunchData());
+ }
+ return false;
+ }
+
+ boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
+ final int keyCode = event.getKeyCode();
+ final int repeatCount = event.getRepeatCount();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final int metaState = event.getModifiers();
+ final int deviceId = event.getDeviceId();
+ final int displayId = event.getDisplayId();
+
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_SPACE:
+ if (down && repeatCount == 0) {
+ // Handle keyboard layout switching. (CTRL + SPACE)
+ if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
+ KeyEvent.META_CTRL_ON)) {
+ return handleKeyGesture(deviceId, new int[]{keyCode},
+ KeyEvent.META_CTRL_ON | (event.isShiftPressed()
+ ? KeyEvent.META_SHIFT_ON : 0),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_Z:
+ if (down && KeyEvent.metaStateHasModifiers(metaState,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON)) {
+ // Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users.
+ return handleKeyGesture(deviceId, new int[]{keyCode},
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
+ }
+ break;
+ case KeyEvent.KEYCODE_SYSRQ:
+ if (down && repeatCount == 0) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
+ }
+ break;
+ case KeyEvent.KEYCODE_ESCAPE:
+ if (down && KeyEvent.metaStateHasNoModifiers(metaState) && repeatCount == 0) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, /* appLaunchData = */null);
+ }
+ break;
}
+
return false;
}
+ private void handleMultiKeyGesture(int[] keycodes,
+ @KeyGestureEvent.KeyGestureType int gestureType, int action, int flags) {
+ handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
+ gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags,
+ /* appLaunchData = */null);
+ }
+
+ private void handleTouchpadGesture(@KeyGestureEvent.KeyGestureType int keyGestureType,
+ @Nullable AppLaunchData appLaunchData) {
+ handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, new int[0], /* modifierState= */0,
+ keyGestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ Display.DEFAULT_DISPLAY, /* focusedToken = */null, /* flags = */0, appLaunchData);
+ }
+
@VisibleForTesting
boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
@KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId,
- IBinder focusedToken, int flags) {
+ @Nullable IBinder focusedToken, int flags, @Nullable AppLaunchData appLaunchData) {
return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes,
- modifierState, gestureType, action, displayId, flags), focusedToken);
+ modifierState, gestureType, action, displayId, flags, appLaunchData), focusedToken);
}
private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) {
@@ -614,33 +935,57 @@ final class KeyGestureController {
// TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally
// should not rely on PWM to tell us about the gesture start and end.
AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
- gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0);
+ gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+ /* flags = */0, /* appLaunchData = */null);
mHandler.obtainMessage(MSG_NOTIFY_KEY_GESTURE_EVENT, event).sendToTarget();
}
public void handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
@KeyGestureEvent.KeyGestureType int gestureType) {
AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
- gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0);
+ gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+ /* flags = */0, /* appLaunchData = */null);
handleKeyGesture(event, null /*focusedToken*/);
}
+ public void handleTouchpadGesture(int touchpadGestureType) {
+ // Handle custom shortcuts
+ InputGestureData customGesture;
+ synchronized (mUserLock) {
+ customGesture = mInputGestureManager.getCustomGestureForTouchpadGesture(mCurrentUserId,
+ touchpadGestureType);
+ }
+ if (customGesture == null) {
+ return;
+ }
+ handleTouchpadGesture(customGesture.getAction().keyGestureType(),
+ customGesture.getAction().appLaunchData());
+ }
+
+ @MainThread
+ public void setCurrentUserId(@UserIdInt int userId) {
+ synchronized (mUserLock) {
+ mCurrentUserId = userId;
+ }
+ mHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+
@MainThread
private void notifyKeyGestureEvent(AidlKeyGestureEvent event) {
InputDevice device = getInputDevice(event.deviceId);
- if (device == null || device.isVirtual()) {
+ if (device == null) {
return;
}
+ KeyGestureEvent keyGestureEvent = new KeyGestureEvent(event);
if (event.action == KeyGestureEvent.ACTION_GESTURE_COMPLETE) {
KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, event.keycodes,
- event.modifierState,
- KeyGestureEvent.keyGestureTypeToLogEvent(event.gestureType));
+ event.modifierState, keyGestureEvent.getLogEvent());
}
notifyAllListeners(event);
while (mLastHandledEvents.size() >= MAX_TRACKED_EVENTS) {
mLastHandledEvents.removeFirst();
}
- mLastHandledEvents.addLast(new KeyGestureEvent(event));
+ mLastHandledEvents.addLast(keyGestureEvent);
}
@MainThread
@@ -663,6 +1008,17 @@ final class KeyGestureController {
AidlKeyGestureEvent event = (AidlKeyGestureEvent) msg.obj;
notifyKeyGestureEvent(event);
break;
+ case MSG_PERSIST_CUSTOM_GESTURES: {
+ final int userId = (Integer) msg.obj;
+ persistInputGestures(userId);
+ break;
+ }
+ case MSG_LOAD_CUSTOM_GESTURES: {
+ final int userId = (Integer) msg.obj;
+ loadInputGestures(userId);
+ break;
+ }
+
}
return true;
}
@@ -704,6 +1060,59 @@ final class KeyGestureController {
}
}
+ @BinderThread
+ @InputManager.CustomInputGestureResult
+ public int addCustomInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData inputGestureData) {
+ final int result = mInputGestureManager.addCustomInputGesture(userId,
+ new InputGestureData(inputGestureData));
+ if (result == InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) {
+ mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+ return result;
+ }
+
+ @BinderThread
+ @InputManager.CustomInputGestureResult
+ public int removeCustomInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData inputGestureData) {
+ final int result = mInputGestureManager.removeCustomInputGesture(userId,
+ new InputGestureData(inputGestureData));
+ if (result == InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) {
+ mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+ return result;
+ }
+
+ @BinderThread
+ public void removeAllCustomInputGestures(@UserIdInt int userId,
+ @Nullable InputGestureData.Filter filter) {
+ mInputGestureManager.removeAllCustomInputGestures(userId, filter);
+ mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+
+ @BinderThread
+ public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId,
+ @Nullable InputGestureData.Filter filter) {
+ List<InputGestureData> customGestures = mInputGestureManager.getCustomInputGestures(userId,
+ filter);
+ AidlInputGestureData[] result = new AidlInputGestureData[customGestures.size()];
+ for (int i = 0; i < customGestures.size(); i++) {
+ result[i] = customGestures.get(i).getAidlData();
+ }
+ return result;
+ }
+
+ @BinderThread
+ public AidlInputGestureData[] getAppLaunchBookmarks() {
+ List<InputGestureData> bookmarks = mAppLaunchShortcutManager.getBookmarks();
+ AidlInputGestureData[] result = new AidlInputGestureData[bookmarks.size()];
+ for (int i = 0; i < bookmarks.size(); i++) {
+ result[i] = bookmarks.get(i).getAidlData();
+ }
+ return result;
+ }
+
private void onKeyGestureEventListenerDied(int pid) {
synchronized (mKeyGestureEventListenerRecords) {
mKeyGestureEventListenerRecords.remove(pid);
@@ -775,12 +1184,41 @@ final class KeyGestureController {
}
}
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException {
+ mAppLaunchShortcutManager.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+ }
+
+ public List<InputGestureData> getBookmarks() {
+ return mAppLaunchShortcutManager.getBookmarks();
+ }
+
private void onKeyGestureHandlerDied(int pid) {
synchronized (mKeyGestureHandlerRecords) {
mKeyGestureHandlerRecords.remove(pid);
}
}
+ private void persistInputGestures(int userId) {
+ synchronized (mInputDataStore) {
+ final List<InputGestureData> inputGestureDataList =
+ mInputGestureManager.getCustomInputGestures(userId,
+ null);
+ mInputDataStore.saveInputGestures(userId, inputGestureDataList);
+ }
+ }
+
+ private void loadInputGestures(int userId) {
+ synchronized (mInputDataStore) {
+ mInputGestureManager.removeAllCustomInputGestures(userId, null);
+ final List<InputGestureData> inputGestureDataList = mInputDataStore.loadInputGestures(
+ userId);
+ for (final InputGestureData inputGestureData : inputGestureDataList) {
+ mInputGestureManager.addCustomInputGesture(userId, inputGestureData);
+ }
+ }
+ }
+
// A record of a registered key gesture event listener from one process.
private class KeyGestureHandlerRecord implements IBinder.DeathRecipient {
public final int mPid;
@@ -822,6 +1260,27 @@ final class KeyGestureController {
}
}
+ private class SettingsObserver extends ContentObserver {
+ private SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ private void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_HUSH_GESTURE), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.KEY_CHORD_POWER_VOLUME_UP), false, this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ initBehaviorsFromSettings();
+ }
+ }
+
@Nullable
private InputDevice getInputDevice(int deviceId) {
InputManager inputManager = mContext.getSystemService(InputManager.class);
@@ -830,7 +1289,7 @@ final class KeyGestureController {
private AidlKeyGestureEvent createKeyGestureEvent(int deviceId, int[] keycodes,
int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action,
- int displayId, int flags) {
+ int displayId, int flags, @Nullable AppLaunchData appLaunchData) {
AidlKeyGestureEvent event = new AidlKeyGestureEvent();
event.deviceId = deviceId;
event.keycodes = keycodes;
@@ -839,18 +1298,33 @@ final class KeyGestureController {
event.action = action;
event.displayId = displayId;
event.flags = flags;
+ if (appLaunchData != null) {
+ if (appLaunchData instanceof AppLaunchData.CategoryData categoryData) {
+ event.appLaunchCategory = categoryData.getCategory();
+ } else if (appLaunchData instanceof AppLaunchData.RoleData roleData) {
+ event.appLaunchRole = roleData.getRole();
+ } else if (appLaunchData instanceof AppLaunchData.ComponentData componentData) {
+ event.appLaunchPackageName = componentData.getPackageName();
+ event.appLaunchClassName = componentData.getClassName();
+ } else {
+ throw new IllegalArgumentException("AppLaunchData type is invalid!");
+ }
+ }
return event;
}
public void dump(IndentingPrintWriter ipw) {
ipw.println("KeyGestureController:");
ipw.increaseIndent();
+ ipw.println("mCurrentUserId = " + mCurrentUserId);
ipw.println("mSystemPid = " + mSystemPid);
ipw.println("mPendingMetaAction = " + mPendingMetaAction);
ipw.println("mPendingCapsLockToggle = " + mPendingCapsLockToggle);
ipw.println("mPendingHideRecentSwitcher = " + mPendingHideRecentSwitcher);
ipw.println("mSearchKeyBehavior = " + mSearchKeyBehavior);
ipw.println("mSettingsKeyBehavior = " + mSettingsKeyBehavior);
+ ipw.println("mRingerToggleChord = " + mRingerToggleChord);
+ ipw.println("mPowerVolUpBehavior = " + mPowerVolUpBehavior);
ipw.print("mKeyGestureEventListenerRecords = {");
synchronized (mKeyGestureEventListenerRecords) {
int size = mKeyGestureEventListenerRecords.size();
@@ -881,5 +1355,8 @@ final class KeyGestureController {
ipw.println(ev);
}
ipw.decreaseIndent();
+ mKeyCombinationManager.dump("", ipw);
+ mAppLaunchShortcutManager.dump(ipw);
+ mInputGestureManager.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index c3205afe14f2..16368c7678d1 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -20,6 +20,7 @@ import android.animation.ValueAnimator;
import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.input.IKeyboardBacklightListener;
import android.hardware.input.IKeyboardBacklightState;
@@ -32,6 +33,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UEventObserver;
+import android.sysprop.InputProperties;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -46,7 +48,6 @@ import java.io.PrintWriter;
import java.time.Duration;
import java.util.Arrays;
import java.util.Objects;
-import java.util.OptionalInt;
import java.util.TreeSet;
/**
@@ -62,6 +63,10 @@ final class KeyboardBacklightController implements
// 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // To disable Framework controlled keyboard backlight animation run:
+ // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
+ private final boolean mKeyboardBacklightAnimationEnabled;
+
private enum Direction {
DIRECTION_UP, DIRECTION_DOWN
}
@@ -81,17 +86,11 @@ final class KeyboardBacklightController implements
private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight";
@VisibleForTesting
- static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis();
-
- @VisibleForTesting
static final int[] DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL =
new int[DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS + 1];
private final Context mContext;
private final NativeInputManagerService mNative;
- // The PersistentDataStore should be locked before use.
- @GuardedBy("mDataStore")
- private final PersistentDataStore mDataStore;
private final Handler mHandler;
private final AnimatorFactory mAnimatorFactory;
private final UEventManager mUEventManager;
@@ -112,6 +111,7 @@ final class KeyboardBacklightController implements
private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
private int mAmbientBacklightValue = 0;
+ private final int mUserInactivityThresholdMs;
static {
// Fixed brightness levels to avoid issues when converting back and forth from the
@@ -124,21 +124,24 @@ final class KeyboardBacklightController implements
}
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper, UEventManager uEventManager) {
- this(context, nativeService, dataStore, looper, ValueAnimator::ofInt, uEventManager);
+ Looper looper, UEventManager uEventManager) {
+ this(context, nativeService, looper, ValueAnimator::ofInt, uEventManager);
}
@VisibleForTesting
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory,
- UEventManager uEventManager) {
+ Looper looper, AnimatorFactory animatorFactory, UEventManager uEventManager) {
mContext = context;
mNative = nativeService;
- mDataStore = dataStore;
mHandler = new Handler(looper, this::handleMessage);
mAnimatorFactory = animatorFactory;
mAmbientController = new AmbientKeyboardBacklightController(context, looper);
mUEventManager = uEventManager;
+ Resources res = mContext.getResources();
+ mUserInactivityThresholdMs = res.getInteger(
+ com.android.internal.R.integer.config_keyboardBacklightTimeoutMs);
+ mKeyboardBacklightAnimationEnabled =
+ InputProperties.enable_keyboard_backlight_animation().orElse(false);
}
@Override
@@ -162,10 +165,8 @@ final class KeyboardBacklightController implements
}
}, UEVENT_KEYBOARD_BACKLIGHT_TAG);
- if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- // Start ambient backlight controller
- mAmbientController.systemRunning();
- }
+ // Start ambient backlight controller
+ mAmbientController.systemRunning();
}
@Override
@@ -227,9 +228,6 @@ final class KeyboardBacklightController implements
// level through keyboard up/down button
updateAmbientLightListener();
- maybeBackupBacklightBrightness(inputDevice, state.mLight,
- state.mBrightnessValueForLevel[newBrightnessLevel]);
-
if (DEBUG) {
Slog.d(TAG,
"Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
@@ -246,47 +244,6 @@ final class KeyboardBacklightController implements
}
}
- private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
- int brightnessValue) {
- // Don't back up or restore when ALS based keyboard backlight is enabled
- if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- return;
- }
- synchronized (mDataStore) {
- try {
- mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
- keyboardBacklight.getId(),
- brightnessValue);
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
- // Don't back up or restore when ALS based keyboard backlight is enabled
- if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- return;
- }
- KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
- OptionalInt brightness;
- synchronized (mDataStore) {
- brightness = mDataStore.getKeyboardBacklightBrightness(
- inputDevice.getDescriptor(), keyboardBacklight.getId());
- }
- if (state != null && brightness.isPresent()) {
- int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
- int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
- if (newLevel < 0) {
- newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
- }
- state.setBrightnessLevel(newLevel);
- if (DEBUG) {
- Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
- }
- }
- }
-
private void handleUserActivity() {
// Ignore user activity if device is not interactive. When device becomes interactive, we
// will send another user activity to turn backlight on.
@@ -300,7 +257,7 @@ final class KeyboardBacklightController implements
}
mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
- SystemClock.uptimeMillis() + USER_INACTIVITY_THRESHOLD_MILLIS);
+ SystemClock.uptimeMillis() + mUserInactivityThresholdMs);
}
private void handleUserInactivity() {
@@ -391,7 +348,6 @@ final class KeyboardBacklightController implements
}
// The keyboard backlight was added or changed.
mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
- maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
}
private InputDevice getInputDevice(int deviceId) {
@@ -470,9 +426,6 @@ final class KeyboardBacklightController implements
}
private void updateAmbientLightListener() {
- if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- return;
- }
boolean needToListenAmbientLightSensor = false;
for (int i = 0; i < mKeyboardBacklights.size(); i++) {
needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
@@ -553,8 +506,7 @@ final class KeyboardBacklightController implements
private int mBrightnessLevel;
private ValueAnimator mAnimator;
private final int[] mBrightnessValueForLevel;
- private boolean mUseAmbientController =
- InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
+ private boolean mUseAmbientController = true;
KeyboardBacklightState(int deviceId, Light light) {
mDeviceId = deviceId;
@@ -563,9 +515,6 @@ final class KeyboardBacklightController implements
}
private int[] setupBrightnessLevels() {
- if (!InputFeatureFlagProvider.isKeyboardBacklightCustomLevelsEnabled()) {
- return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
- }
int[] customLevels = mLight.getPreferredBrightnessLevels();
if (customLevels == null || customLevels.length == 0) {
return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
@@ -625,7 +574,7 @@ final class KeyboardBacklightController implements
if (fromValue == toValue) {
return;
}
- if (InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled()) {
+ if (mKeyboardBacklightAnimationEnabled) {
startAnimation(fromValue, toValue);
} else {
mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0));
diff --git a/services/core/java/com/android/server/input/KeyboardGlyphManager.java b/services/core/java/com/android/server/input/KeyboardGlyphManager.java
index f59d72b0c0b6..6f19a3f14758 100644
--- a/services/core/java/com/android/server/input/KeyboardGlyphManager.java
+++ b/services/core/java/com/android/server/input/KeyboardGlyphManager.java
@@ -149,17 +149,17 @@ public final class KeyboardGlyphManager implements InputManager.InputDeviceListe
continue;
}
final ActivityInfo activityInfo = resolveInfo.activityInfo;
- KeyGlyphMapData data = getKeyboardGlyphMapsInPackage(pm, activityInfo);
- if (data == null) {
+ List<KeyGlyphMapData> data = getKeyboardGlyphMapsInPackage(pm, activityInfo);
+ if (data == null || data.isEmpty()) {
continue;
}
- glyphMaps.add(data);
+ glyphMaps.addAll(data);
}
return glyphMaps;
}
@Nullable
- private KeyGlyphMapData getKeyboardGlyphMapsInPackage(PackageManager pm,
+ private List<KeyGlyphMapData> getKeyboardGlyphMapsInPackage(PackageManager pm,
@NonNull ActivityInfo receiver) {
Bundle metaData = receiver.metaData;
if (metaData == null) {
@@ -175,6 +175,7 @@ public final class KeyboardGlyphManager implements InputManager.InputDeviceListe
try {
Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+ List<KeyGlyphMapData> glyphMaps = new ArrayList<>();
try (XmlResourceParser parser = resources.getXml(configResId)) {
XmlUtils.beginDocument(parser, TAG_KEYBOARD_GLYPH_MAPS);
@@ -193,13 +194,14 @@ public final class KeyboardGlyphManager implements InputManager.InputDeviceListe
int vendor = a.getInt(R.styleable.KeyboardGlyphMap_vendorId, -1);
int product = a.getInt(R.styleable.KeyboardGlyphMap_productId, -1);
if (glyphMapRes != 0 && vendor != -1 && product != -1) {
- return new KeyGlyphMapData(receiver.packageName, receiver.name,
- glyphMapRes, vendor, product);
+ glyphMaps.add(new KeyGlyphMapData(receiver.packageName, receiver.name,
+ glyphMapRes, vendor, product));
}
} finally {
a.recycle();
}
}
+ return glyphMaps;
}
} catch (Exception ex) {
Slog.w(TAG, "Could not parse keyboard glyph map resource from receiver "
diff --git a/services/core/java/com/android/server/input/KeyboardLedController.java b/services/core/java/com/android/server/input/KeyboardLedController.java
index 5c404a2ae6e7..a2940d54fe4d 100644
--- a/services/core/java/com/android/server/input/KeyboardLedController.java
+++ b/services/core/java/com/android/server/input/KeyboardLedController.java
@@ -46,11 +46,13 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
private static final String TAG = KeyboardLedController.class.getSimpleName();
private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
private static final int MSG_UPDATE_MIC_MUTE_LED_STATE = 2;
+ private static final int MSG_UPDATE_AUDIO_MUTE_LED_STATE = 3;
private final Context mContext;
private final Handler mHandler;
private final NativeInputManagerService mNative;
private final SparseArray<InputDevice> mKeyboardsWithMicMuteLed = new SparseArray<>();
+ private final SparseArray<InputDevice> mKeyboardsWithVolumeMuteLed = new SparseArray<>();
@NonNull
private InputManager mInputManager;
@NonNull
@@ -66,6 +68,17 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
}
};
+ private BroadcastReceiver mVolumeMuteIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_AUDIO_MUTE_LED_STATE);
+ mHandler.sendMessage(msg);
+ }
+ }
+ };
+
KeyboardLedController(Context context, Looper looper,
NativeInputManagerService nativeService) {
mContext = context;
@@ -83,6 +96,9 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
case MSG_UPDATE_MIC_MUTE_LED_STATE:
updateMicMuteLedState();
return true;
+ case MSG_UPDATE_AUDIO_MUTE_LED_STATE:
+ updateVolumeMuteLedState();
+ return true;
}
return false;
}
@@ -105,6 +121,21 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
}
}
+ private void updateVolumeMuteLedState() {
+ int color = mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE)
+ ? Color.WHITE : Color.TRANSPARENT;
+ for (int i = 0; i < mKeyboardsWithVolumeMuteLed.size(); i++) {
+ InputDevice device = mKeyboardsWithVolumeMuteLed.valueAt(i);
+ if (device != null) {
+ int deviceId = device.getId();
+ Light light = getKeyboardVolumeMuteLight(device);
+ if (light != null) {
+ mNative.setLightColor(deviceId, light.getId(), color);
+ }
+ }
+ }
+ }
+
private Light getKeyboardMicMuteLight(InputDevice device) {
for (Light light : device.getLightsManager().getLights()) {
if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_MIC_MUTE
@@ -115,6 +146,16 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
return null;
}
+ private Light getKeyboardVolumeMuteLight(InputDevice device) {
+ for (Light light : device.getLightsManager().getLights()) {
+ if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_VOLUME_MUTE
+ && light.hasBrightnessControl()) {
+ return light;
+ }
+ }
+ return null;
+ }
+
/** Called when the system is ready for us to start third-party code. */
public void systemRunning() {
mSensorPrivacyManager = Objects.requireNonNull(
@@ -131,6 +172,12 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED),
null,
mHandler);
+ mContext.registerReceiverAsUser(
+ mVolumeMuteIntentReceiver,
+ UserHandle.ALL,
+ new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION),
+ null,
+ mHandler);
}
@Override
@@ -141,6 +188,7 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
@Override
public void onInputDeviceRemoved(int deviceId) {
mKeyboardsWithMicMuteLed.remove(deviceId);
+ mKeyboardsWithVolumeMuteLed.remove(deviceId);
}
@Override
@@ -154,6 +202,11 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
Message msg = Message.obtain(mHandler, MSG_UPDATE_MIC_MUTE_LED_STATE);
mHandler.sendMessage(msg);
}
+ if (getKeyboardVolumeMuteLight(inputDevice) != null) {
+ mKeyboardsWithVolumeMuteLed.put(deviceId, inputDevice);
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_AUDIO_MUTE_LED_STATE);
+ mHandler.sendMessage(msg);
+ }
}
/** Dump the diagnostic information */
@@ -167,5 +220,14 @@ public final class KeyboardLedController implements InputManager.InputDeviceList
+ getKeyboardMicMuteLight(inputDevice).toString());
}
ipw.decreaseIndent();
+ ipw.println(TAG + ": " + mKeyboardsWithVolumeMuteLed.size()
+ + " keyboard volume mute lights");
+ ipw.increaseIndent();
+ for (int i = 0; i < mKeyboardsWithVolumeMuteLed.size(); i++) {
+ InputDevice inputDevice = mKeyboardsWithVolumeMuteLed.valueAt(i);
+ ipw.println(i + " " + inputDevice.getName() + ": "
+ + getKeyboardVolumeMuteLight(inputDevice).toString());
+ }
+ ipw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index d17e256e34fc..c72f7c076a83 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.input;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.display.DisplayTopologyGraph;
import android.hardware.display.DisplayViewport;
import android.hardware.input.InputSensorInfo;
import android.hardware.lights.Light;
@@ -42,6 +43,8 @@ interface NativeInputManagerService {
void setDisplayViewports(DisplayViewport[] viewports);
+ void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
int getScanCodeState(int deviceId, int sourceMask, int scanCode);
int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
@@ -98,6 +101,8 @@ interface NativeInputManagerService {
void toggleCapsLock(int deviceId);
+ void resetLockedModifierState();
+
void displayRemoved(int displayId);
void setInputDispatchMode(boolean enabled, boolean frozen);
@@ -127,6 +132,10 @@ interface NativeInputManagerService {
void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ void setMouseSwapPrimaryButtonEnabled(boolean enabled);
+
void setTouchpadPointerSpeed(int speed);
void setTouchpadNaturalScrollingEnabled(boolean enabled);
@@ -139,9 +148,13 @@ interface NativeInputManagerService {
void setTouchpadRightClickZoneEnabled(boolean enabled);
+ void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+ void setTouchpadSystemGesturesEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
- void setInteractive(boolean interactive);
+ void setNonInteractiveDisplays(int[] displayIds);
void reloadCalibration();
@@ -283,6 +296,17 @@ interface NativeInputManagerService {
*/
int getLastUsedInputDeviceId();
+ /**
+ * Set whether the given input device can wake up the kernel from sleep
+ * when it generates input events. By default, usually only internal (built-in)
+ * input devices can wake the kernel from sleep. For an external input device
+ * that supports remote wakeup to be able to wake the kernel, this must be called
+ * after each time the device is connected/added.
+ *
+ * Returns true if setting power wakeup was successful.
+ */
+ boolean setKernelWakeEnabled(int deviceId, boolean enabled);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -302,6 +326,9 @@ interface NativeInputManagerService {
public native void setDisplayViewports(DisplayViewport[] viewports);
@Override
+ public native void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
+ @Override
public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
@Override
@@ -353,6 +380,9 @@ interface NativeInputManagerService {
public native void toggleCapsLock(int deviceId);
@Override
+ public native void resetLockedModifierState();
+
+ @Override
public native void displayRemoved(int displayId);
@Override
@@ -388,6 +418,12 @@ interface NativeInputManagerService {
public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@Override
+ public native void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ @Override
+ public native void setMouseSwapPrimaryButtonEnabled(boolean enabled);
+
+ @Override
public native void setTouchpadPointerSpeed(int speed);
@Override
@@ -406,10 +442,16 @@ interface NativeInputManagerService {
public native void setTouchpadRightClickZoneEnabled(boolean enabled);
@Override
+ public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+ @Override
+ public native void setTouchpadSystemGesturesEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
- public native void setInteractive(boolean interactive);
+ public native void setNonInteractiveDisplays(int[] displayIds);
@Override
public native void reloadCalibration();
@@ -563,5 +605,8 @@ interface NativeInputManagerService {
@Override
public native int getLastUsedInputDeviceId();
+
+ @Override
+ public native boolean setKernelWakeEnabled(int deviceId, boolean enabled);
}
}
diff --git a/services/core/java/com/android/server/input/PointerIconCache.java b/services/core/java/com/android/server/input/PointerIconCache.java
index 297cd68d5d3d..e16031cb664a 100644
--- a/services/core/java/com/android/server/input/PointerIconCache.java
+++ b/services/core/java/com/android/server/input/PointerIconCache.java
@@ -27,6 +27,7 @@ import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
@@ -34,6 +35,7 @@ import android.view.DisplayInfo;
import android.view.PointerIcon;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.UiThread;
import java.util.Objects;
@@ -51,7 +53,7 @@ final class PointerIconCache {
private final NativeInputManagerService mNative;
// We use the UI thread for loading pointer icons.
- private final Handler mUiThreadHandler = UiThread.getHandler();
+ private final Handler mUiThreadHandler;
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
@@ -70,6 +72,9 @@ final class PointerIconCache {
POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
private float mPointerIconScale = DEFAULT_POINTER_SCALE;
+ // Note that android doesn't have SparseFloatArray, so this falls back to use double instead.
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private final SparseDoubleArray mAccessibilityScaleFactorPerDisplay = new SparseDoubleArray();
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@@ -86,6 +91,7 @@ final class PointerIconCache {
mLoadedPointerIconsByDisplayAndType.remove(displayId);
mDisplayContexts.remove(displayId);
mDisplayDensities.delete(displayId);
+ mAccessibilityScaleFactorPerDisplay.delete(displayId);
}
}
@@ -96,8 +102,15 @@ final class PointerIconCache {
};
/* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
+ this(context, nativeService, UiThread.getHandler());
+ }
+
+ @VisibleForTesting
+ /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService,
+ Handler handler) {
mContext = context;
mNative = nativeService;
+ mUiThreadHandler = handler;
}
public void systemRunning() {
@@ -134,6 +147,11 @@ final class PointerIconCache {
mUiThreadHandler.post(() -> handleSetPointerScale(scale));
}
+ /** Set the scale for accessibility (magnification) for vector pointer icons. */
+ public void setAccessibilityScaleFactor(int displayId, float scaleFactor) {
+ mUiThreadHandler.post(() -> handleAccessibilityScaleFactor(displayId, scaleFactor));
+ }
+
/**
* Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
* it isn't already cached.
@@ -155,8 +173,10 @@ final class PointerIconCache {
/* force= */ true);
theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(mPointerIconStrokeStyle),
/* force= */ true);
+ final float scale = mPointerIconScale
+ * (float) mAccessibilityScaleFactorPerDisplay.get(displayId, 1f);
icon = PointerIcon.getLoadedSystemIcon(new ContextThemeWrapper(context, theme),
- type, mUseLargePointerIcons, mPointerIconScale);
+ type, mUseLargePointerIcons, scale);
iconsByType.put(type, icon);
}
return Objects.requireNonNull(icon);
@@ -261,6 +281,19 @@ final class PointerIconCache {
mNative.reloadPointerIcons();
}
+ @android.annotation.UiThread
+ private void handleAccessibilityScaleFactor(int displayId, float scale) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ if (mAccessibilityScaleFactorPerDisplay.get(displayId, 1f) == scale) {
+ return;
+ }
+ mAccessibilityScaleFactorPerDisplay.put(displayId, scale);
+ // Clear cached icons on the display.
+ mLoadedPointerIconsByDisplayAndType.remove(displayId);
+ }
+ mNative.reloadPointerIcons();
+ }
+
// Updates the cached display density for the given displayId, and returns true if
// the cached density changed.
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index a1e5ebc002a5..cf0c5b094c4d 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -180,7 +180,8 @@ public class TouchpadDebugView extends LinearLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) {
+ if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
+ || event.getClassification() == MotionEvent.CLASSIFICATION_PINCH) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 146ce1732070..46be1ca0eec2 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -27,6 +27,7 @@ import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -59,8 +60,14 @@ final class AdditionalSubtypeUtils {
private static final String NODE_SUBTYPE = "subtype";
private static final String NODE_IMI = "imi";
private static final String ATTR_ID = "id";
+ /** The resource ID of the subtype name. */
private static final String ATTR_LABEL = "label";
+ /** The untranslatable name of the subtype. */
private static final String ATTR_NAME_OVERRIDE = "nameOverride";
+ /** The layout label string resource identifier. */
+ private static final String ATTR_LAYOUT_LABEL = "layoutLabel";
+ /** The non-localized layout label. */
+ private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized";
private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag";
private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType";
private static final String ATTR_ICON = "icon";
@@ -173,6 +180,11 @@ final class AdditionalSubtypeUtils {
out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
+ if (Flags.imeSwitcherRevampApi()) {
+ out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource());
+ out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED,
+ subtype.getLayoutLabelNonLocalized().toString());
+ }
ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag();
if (pkLanguageTag != null) {
out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG,
@@ -264,6 +276,16 @@ final class AdditionalSubtypeUtils {
final int label = parser.getAttributeInt(null, ATTR_LABEL);
final String untranslatableName = parser.getAttributeValue(null,
ATTR_NAME_OVERRIDE);
+ final int layoutLabelResource;
+ final String layoutLabelNonLocalized;
+ if (Flags.imeSwitcherRevampApi()) {
+ layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL);
+ layoutLabelNonLocalized = parser.getAttributeValue(null,
+ ATTR_LAYOUT_LABEL_NON_LOCALIZED);
+ } else {
+ layoutLabelResource = 0;
+ layoutLabelNonLocalized = null;
+ }
final String pkLanguageTag = parser.getAttributeValue(null,
ATTR_NAME_PK_LANGUAGE_TAG);
final String pkLayoutType = parser.getAttributeValue(null,
@@ -283,6 +305,7 @@ final class AdditionalSubtypeUtils {
final InputMethodSubtype.InputMethodSubtypeBuilder
builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(label)
+ .setLayoutLabelResource(layoutLabelResource)
.setPhysicalKeyboardHint(
pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
pkLayoutType == null ? "" : pkLayoutType)
@@ -301,6 +324,9 @@ final class AdditionalSubtypeUtils {
if (untranslatableName != null) {
builder.setSubtypeNameOverride(untranslatableName);
}
+ if (layoutLabelNonLocalized != null) {
+ builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized);
+ }
tempSubtypesArray.add(builder.build());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index d13244905633..da2089131b9f 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -19,13 +19,19 @@ package com.android.server.inputmethod;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
import android.os.InputConfig;
import android.os.Process;
+import android.util.Slog;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.view.inputmethod.Flags;
import com.android.server.input.InputManagerService;
@@ -39,8 +45,8 @@ final class HandwritingEventReceiverSurface {
private final SurfaceControl mInputSurface;
private boolean mIsIntercepting;
- HandwritingEventReceiverSurface(String name, int displayId, @NonNull SurfaceControl sc,
- @NonNull InputChannel inputChannel) {
+ HandwritingEventReceiverSurface(Context context, String name, int displayId,
+ @NonNull SurfaceControl sc, @NonNull InputChannel inputChannel) {
mClientChannel = inputChannel;
mInputSurface = sc;
@@ -59,15 +65,31 @@ final class HandwritingEventReceiverSurface {
| InputConfig.SPY
| InputConfig.INTERCEPTS_STYLUS;
- // Configure the surface to receive stylus events across the entire display.
- mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+ Rect bounds = null;
+ if (Flags.adaptiveHandwritingBounds()) {
+ mWindowHandle.setTouchableRegionCrop(mInputSurface);
+ // Default touchable area to getMaximumWindowMetrics()
+ WindowMetrics windowMetrics = context.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics();
+ bounds = windowMetrics.getBounds();
+ if (DEBUG) Slog.d(TAG, "initial handwriting touchable bounds: " + bounds);
+ mWindowHandle.setTouchableRegion(windowMetrics.getBounds());
+ } else {
+ // Configure the surface to receive stylus events across the entire display.
+ mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+ }
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
t.setPosition(mInputSurface, 0, 0);
- t.setCrop(mInputSurface, null /* crop to parent surface */);
+ if (Flags.adaptiveHandwritingBounds()) {
+ // crop to parent surface if null, else bounds.
+ t.setCrop(mInputSurface, bounds);
+ } else {
+ t.setCrop(mInputSurface, null /* crop to parent surface */);
+ }
t.show(mInputSurface);
t.apply();
@@ -79,12 +101,23 @@ final class HandwritingEventReceiverSurface {
mWindowHandle.ownerUid = imeUid;
mWindowHandle.inputConfig &= ~InputConfig.SPY;
+ if (Flags.adaptiveHandwritingBounds()) {
+ // watch outside touch to finish handwriting.
+ mWindowHandle.inputConfig |= InputConfig.WATCH_OUTSIDE_TOUCH;
+ }
new SurfaceControl.Transaction()
.setInputWindowInfo(mInputSurface, mWindowHandle)
.apply();
mIsIntercepting = true;
}
+ void setTouchableRegion(Region touchableRegion) {
+ mWindowHandle.touchableRegion.set(touchableRegion);
+ new SurfaceControl.Transaction()
+ .setInputWindowInfo(mInputSurface, mWindowHandle)
+ .apply();
+ }
+
void setNotTouchable(boolean notTouchable) {
if (notTouchable) {
mWindowHandle.inputConfig |= InputConfig.NOT_TOUCHABLE;
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index c19cb030ef37..45d7d0464fe1 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -27,6 +27,7 @@ import android.annotation.UiThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.graphics.Region;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
import android.os.Handler;
@@ -139,7 +140,7 @@ final class HandwritingModeController {
}
mHandwritingSurface = new HandwritingEventReceiverSurface(
- name, displayId, surface, channel);
+ mContext, name, displayId, surface, channel);
// Use a dup of the input channel so that event processing can be paused by disposing the
// event receiver without causing a fd hangup.
@@ -163,6 +164,13 @@ final class HandwritingModeController {
mHandwritingSurface.setNotTouchable(notTouchable);
}
+ void setHandwritingTouchableRegion(Region region) {
+ if (!getCurrentRequestId().isPresent()) {
+ return;
+ }
+ mHandwritingSurface.setTouchableRegion(region);
+ }
+
boolean isStylusGestureOngoing() {
if (mRecordingGestureAfterStylusUp && !mHandwritingBuffer.isEmpty()) {
// If it is less than AFTER_STYLUS_UP_ALLOW_PERIOD_MS after the stylus up event, return
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index 41313fa1fb2c..ef1220fb1786 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -33,9 +33,6 @@ final class HardwareKeyboardShortcutController {
@GuardedBy("ImfLock.class")
private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
- HardwareKeyboardShortcutController() {
- }
-
@GuardedBy("ImfLock.class")
void update(@NonNull InputMethodSettings settings) {
mSubtypeHandles.clear();
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
index f78ea84efb77..5deed39a2dce 100644
--- a/services/core/java/com/android/server/inputmethod/ImeBindingState.java
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -20,6 +20,7 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCU
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.IBinder;
@@ -86,10 +87,10 @@ final class ImeBindingState {
InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode));
}
- void dump(String prefix, Printer p) {
- p.println(prefix + "mFocusedWindow()=" + mFocusedWindow);
- p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString(
- mFocusedWindowSoftInputMode));
+ void dump(@NonNull Printer p, @NonNull String prefix) {
+ p.println(prefix + "mFocusedWindow=" + mFocusedWindow);
+ p.println(prefix + "mFocusedWindowSoftInputMode="
+ + InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode));
p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index ec1993a9b444..477660daa1de 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -61,6 +61,7 @@ import com.android.internal.inputmethod.UnbindReason;
import com.android.server.EventLogTags;
import com.android.server.wm.WindowManagerInternal;
+import java.io.PrintWriter;
import java.util.concurrent.CountDownLatch;
/**
@@ -733,4 +734,25 @@ final class InputMethodBindingController {
void setBackDisposition(@BackDispositionMode int backDisposition) {
mBackDisposition = backDisposition;
}
+
+ @GuardedBy("ImfLock.class")
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "mSelectedMethodId=" + mSelectedMethodId);
+ pw.println(prefix + "mCurrentSubtype=" + mCurrentSubtype);
+ pw.println(prefix + "mCurSeq=" + mCurSeq);
+ pw.println(prefix + "mCurId=" + mCurId);
+ pw.println(prefix + "mHasMainConnection=" + mHasMainConnection);
+ pw.println(prefix + "mVisibleBound=" + mVisibleBound);
+ pw.println(prefix + "mCurToken=" + mCurToken);
+ pw.println(prefix + "mCurTokenDisplayId=" + mCurTokenDisplayId);
+ pw.println(prefix + "mCurHostInputToken=" + getCurHostInputToken());
+ pw.println(prefix + "mCurIntent=" + mCurIntent);
+ pw.println(prefix + "mCurMethod=" + mCurMethod);
+ pw.println(prefix + "mImeWindowVis=" + mImeWindowVis);
+ pw.println(prefix + "mBackDisposition=" + mBackDisposition);
+ pw.println(prefix + "mDisplayIdToShowIme=" + mDisplayIdToShowIme);
+ pw.println(prefix + "mDeviceIdToShowIme=" + mDeviceIdToShowIme);
+ pw.println(prefix + "mSupportsStylusHw=" + mSupportsStylusHw);
+ pw.println(prefix + "mSupportsConnectionlessStylusHw=" + mSupportsConnectionlessStylusHw);
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
index 6cd2493cfdff..fc4c0fc798db 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
@@ -40,6 +40,7 @@ final class InputMethodDeviceConfigs {
if (KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS.equals(name)) {
mHideImeWhenNoEditorFocus = properties.getBoolean(name,
true /* defaultValue */);
+ break;
}
}
};
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 679d341c6f51..02dd884ad60d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -84,6 +84,8 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.graphics.Region;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
@@ -119,6 +121,7 @@ import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -185,7 +188,6 @@ import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
-import com.android.server.inputmethod.InputMethodMenuControllerNew.MenuItem;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -448,6 +450,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
private AudioManagerInternal mAudioManagerInternal = null;
@Nullable
private VirtualDeviceManagerInternal mVdmInternal = null;
+ @Nullable
+ private DisplayManagerInternal mDisplayManagerInternal = null;
// Mapping from deviceId to the device-specific imeId for that device.
@GuardedBy("ImfLock.class")
@@ -1079,6 +1083,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
AdditionalSubtypeMapRepository.remove(userId);
InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
+ synchronized (ImfLock.class) {
+ final int nextOrCurrentUser = mService.mUserSwitchHandlerTask != null
+ ? mService.mUserSwitchHandlerTask.mToUserId : mService.mCurrentImeUserId;
+ if (!mService.mConcurrentMultiUserModeEnabled && userId == nextOrCurrentUser) {
+ // The current user was removed without an ongoing switch, or the user targeted
+ // by the ongoing switch was removed. Switch to the current non-profile user
+ // to allow starting input on it or one of its profile users later.
+ // Note: non-profile users cannot be removed while they are the current user.
+ final int currentUserId = mService.mActivityManagerInternal.getCurrentUserId();
+ mService.scheduleSwitchUserTaskLocked(currentUserId,
+ null /* clientToBeReset */);
+ }
+ }
}
@Override
@@ -1294,8 +1311,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = bindingController.getSelectedMethodId();
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final InputMethodInfo selectedImi = settings.getMethodMap().get(selectedMethodId);
- if (selectedImi != null && !selectedImi.isSystem()) {
+ if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null
+ && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
@@ -1329,7 +1346,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
+ " prevUserId=" + prevUserId);
}
- // Clean up stuff for mCurrentUserId, which soon becomes the previous user.
+ // Clean up stuff for mCurrentImeUserId, which soon becomes the previous user.
// TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset(prevUserId);
@@ -1533,7 +1550,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- return settings.getMethodMap().get(settings.getSelectedInputMethod());
+ final String selectedImeId;
+ if (Flags.consistentGetCurrentInputMethodInfo()) {
+ final var bindingController = getInputMethodBindingController(userId);
+ synchronized (ImfLock.class) {
+ selectedImeId = bindingController.getSelectedMethodId();
+ }
+ } else {
+ selectedImeId = settings.getSelectedInputMethod();
+ }
+ return settings.getMethodMap().get(selectedImeId);
}
@BinderThread
@@ -2165,7 +2191,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final var bindingController = getInputMethodBindingController(userId);
final int oldDeviceId = bindingController.getDeviceIdToShowIme();
final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
- final int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme);
+ int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme);
+ if (newDeviceId != DEVICE_ID_DEFAULT) {
+ // Only show custom IME on trusted displays.
+ if (mDisplayManagerInternal == null) {
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ }
+ int displayFlags = mDisplayManagerInternal.getDisplayInfo(displayIdToShowIme).flags;
+ if ((displayFlags & Display.FLAG_TRUSTED) != Display.FLAG_TRUSTED) {
+ // If the display is not trusted, fallback to the default device IME.
+ newDeviceId = DEVICE_ID_DEFAULT;
+ }
+ }
bindingController.setDeviceIdToShowIme(newDeviceId);
if (newDeviceId == DEVICE_ID_DEFAULT) {
if (oldDeviceId == DEVICE_ID_DEFAULT) {
@@ -4063,65 +4100,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
}
- /**
- * Gets the list of Input Method Switcher Menu items and the index of the selected item.
- *
- * @param items the list of input method and subtype items.
- * @param selectedImeId the ID of the selected input method.
- * @param selectedSubtypeIndex the index of the selected subtype in the input method's array of
- * subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if no
- * subtype is selected.
- * @param userId the ID of the user for which to get the menu items.
- * @return the list of menu items, and the index of the selected item,
- * or {@code -1} if no item is selected.
- */
- @GuardedBy("ImfLock.class")
- @NonNull
- private Pair<List<MenuItem>, Integer> getInputMethodPickerItems(
- @NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId,
- int selectedSubtypeIndex, @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
- final var settings = InputMethodSettingsRepository.get(userId);
-
- if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
- // TODO(b/351124299): Check if this fallback logic is still necessary.
- final var curSubtype = bindingController.getCurrentInputMethodSubtype();
- if (curSubtype != null) {
- final var curMethodId = bindingController.getSelectedMethodId();
- final var curImi = settings.getMethodMap().get(curMethodId);
- selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
- curImi, curSubtype.hashCode());
- }
- }
-
- // No item is selected by default. When we have a list of explicitly enabled
- // subtypes, the implicit subtype is no longer listed. If the implicit one
- // is still selected, no items will be shown as selected.
- int selectedIndex = -1;
- String prevImeId = null;
- final var menuItems = new ArrayList<MenuItem>();
- for (int i = 0; i < items.size(); i++) {
- final var item = items.get(i);
- final var imeId = item.mImi.getId();
- if (imeId.equals(selectedImeId)) {
- final int subtypeIndex = item.mSubtypeIndex;
- // Check if this is the selected IME-subtype pair.
- if ((subtypeIndex == 0 && selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX)
- || subtypeIndex == NOT_A_SUBTYPE_INDEX
- || subtypeIndex == selectedSubtypeIndex) {
- selectedIndex = i;
- }
- }
- final boolean hasHeader = !imeId.equals(prevImeId);
- final boolean hasDivider = hasHeader && prevImeId != null;
- prevImeId = imeId;
- menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi,
- item.mSubtypeIndex, hasHeader, hasDivider));
- }
-
- return new Pair<>(menuItems, selectedIndex);
- }
-
@IInputMethodManagerImpl.PermissionVerified(allOf = {
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
Manifest.permission.WRITE_SECURE_SETTINGS})
@@ -4799,7 +4777,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
userData.mImeBindingState.mFocusedWindowEditorInfo,
info.focusedWindowName, userData.mImeBindingState.mFocusedWindowSoftInputMode,
reason, userData.mInFullscreenMode, info.requestWindowName,
- info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName));
+ info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName,
+ userId));
if (statsToken != null) {
mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -4957,18 +4936,21 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
+ " preferredInputMethodSubtypeIndex=" + lastInputMethodSubtypeIndex);
}
- final var itemsAndIndex = getInputMethodPickerItems(imList,
- lastInputMethodId, lastInputMethodSubtypeIndex, userId);
- final var menuItems = itemsAndIndex.first;
- final int selectedIndex = itemsAndIndex.second;
-
- if (selectedIndex == -1) {
- Slog.w(TAG, "Switching menu shown with no item selected"
- + ", IME id: " + lastInputMethodId
- + ", subtype index: " + lastInputMethodSubtypeIndex);
+ int selectedSubtypeIndex = lastInputMethodSubtypeIndex;
+ if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
+ // TODO(b/351124299): Check if this fallback logic is still necessary.
+ final var bindingController = getInputMethodBindingController(userId);
+ final var curSubtype = bindingController.getCurrentInputMethodSubtype();
+ if (curSubtype != null) {
+ final var curMethodId = bindingController.getSelectedMethodId();
+ final var curImi = settings.getMethodMap().get(curMethodId);
+ selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
+ curImi, curSubtype.hashCode());
+ }
}
- mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId);
+ mMenuControllerNew.show(imList, lastInputMethodId, selectedSubtypeIndex, displayId,
+ userId);
} else {
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
lastInputMethodId, lastInputMethodSubtypeIndex, imList, userId);
@@ -4988,7 +4970,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
setImeVisibilityOnFocusedWindowClient(false, userData,
- null /* TODO(b329229469) check statsToken */);
+ null /* TODO(b/353463205) check statsToken */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
@@ -6095,45 +6077,40 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
@BinderThread
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
@BinderThread
- private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
- boolean isCritical) {
+ private void dumpAsStringNoCheck(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+ @NonNull String[] args, boolean isCritical) {
final int argUserId = parseUserIdFromDumpArgs(args);
final Printer p = new PrintWriterPrinter(pw);
- p.println("Current Input Method Manager state:");
+ p.println("Input Method Manager Service state:");
p.println(" mSystemReady=" + mSystemReady);
p.println(" mInteractive=" + mIsInteractive);
p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
- p.println(" ENABLE_HIDE_IME_CAPTION_BAR="
- + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
+ final int currentImeUserId;
synchronized (ImfLock.class) {
+ currentImeUserId = mCurrentImeUserId;
+ p.println(" mCurrentImeUserId=" + currentImeUserId);
p.println(" mStylusIds=" + (mStylusIds != null
? Arrays.toString(mStylusIds.toArray()) : ""));
}
+ // TODO(b/305849394): Make mMenuController multi-user aware.
if (Flags.imeSwitcherRevamp()) {
p.println(" mMenuControllerNew:");
- mMenuControllerNew.dump(p, " ");
+ mMenuControllerNew.dump(p, " ");
} else {
p.println(" mMenuController:");
- mMenuController.dump(p, " ");
- }
- if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) {
- mUserDataRepository.forAllUserData(
- u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical));
- } else {
- final int userId = argUserId != UserHandle.USER_NULL ? argUserId : mCurrentImeUserId;
- final var userData = getUserData(userId);
- dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical);
+ mMenuController.dump(p, " ");
}
+ dumpClientController(p);
+ dumpUserRepository(p);
- // TODO(b/365868861): Make StartInputHistory, SoftInputShowHideHistory and ImeTracker per
- // user.
+ // TODO(b/365868861): Make StartInputHistory and ImeTracker multi-user aware.
synchronized (ImfLock.class) {
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
@@ -6145,12 +6122,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
p.println(" mImeTrackerService#History:");
mImeTrackerService.dump(pw, " ");
- dumpUserRepository(p);
- dumpClientStates(p);
+ if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) {
+ mUserDataRepository.forAllUserData(
+ u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical));
+ } else {
+ final int userId = argUserId != UserHandle.USER_NULL ? argUserId : currentImeUserId;
+ final var userData = getUserData(userId);
+ dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical);
+ }
}
@UserIdInt
- private static int parseUserIdFromDumpArgs(String[] args) {
+ private static int parseUserIdFromDumpArgs(@NonNull String[] args) {
final int userIdx = Arrays.binarySearch(args, "--user");
if (userIdx == -1 || userIdx == args.length - 1) {
return UserHandle.USER_NULL;
@@ -6160,44 +6143,37 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// TODO(b/356239178): Update dump format output to better group per-user info.
@BinderThread
- private void dumpAsStringNoCheckForUser(UserData userData, FileDescriptor fd, PrintWriter pw,
- String[] args, boolean isCritical) {
+ private void dumpAsStringNoCheckForUser(@NonNull UserData userData, @NonNull FileDescriptor fd,
+ @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical) {
final Printer p = new PrintWriterPrinter(pw);
- IInputMethodInvoker method;
ClientState client;
+ IInputMethodInvoker method;
p.println(" UserId=" + userData.mUserId);
synchronized (ImfLock.class) {
- final InputMethodSettings settings = InputMethodSettingsRepository.get(
- userData.mUserId);
+ final var bindingController = userData.mBindingController;
+ client = userData.mCurClient;
+ method = bindingController.getCurMethod();
+ p.println(" mBindingController:");
+ bindingController.dump(pw, " ");
+ p.println(" mCurClient=" + client);
+ p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
+ p.println(" mImeBindingState:");
+ userData.mImeBindingState.dump(p, " ");
+ p.println(" mBoundToMethod=" + userData.mBoundToMethod);
+ p.println(" mEnabledSession=" + userData.mEnabledSession);
+ p.println(" mVisibilityStateComputer:");
+ userData.mVisibilityStateComputer.dump(pw, " ");
+ p.println(" mInFullscreenMode=" + userData.mInFullscreenMode);
+
+ final var settings = InputMethodSettingsRepository.get(userData.mUserId);
final List<InputMethodInfo> methodList = settings.getMethodList();
- int numImes = methodList.size();
+ final int numImes = methodList.size();
p.println(" Input Methods:");
for (int i = 0; i < numImes; i++) {
- InputMethodInfo info = methodList.get(i);
+ final InputMethodInfo info = methodList.get(i);
p.println(" InputMethod #" + i + ":");
info.dump(p, " ");
}
- final var bindingController = userData.mBindingController;
- p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
- client = userData.mCurClient;
- p.println(" mCurClient=" + client + " mCurSeq="
- + bindingController.getSequenceNumber());
- p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
- userData.mImeBindingState.dump(/* prefix= */ " ", p);
- p.println(" mCurId=" + bindingController.getCurId());
- p.println(" mHaveConnection=" + bindingController.hasMainConnection());
- p.println(" mBoundToMethod=" + userData.mBoundToMethod);
- p.println(" mVisibleBound=" + bindingController.isVisibleBound());
- p.println(" mCurToken=" + bindingController.getCurToken());
- p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
- p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken());
- p.println(" mCurIntent=" + bindingController.getCurIntent());
- method = bindingController.getCurMethod();
- p.println(" mCurMethod=" + method);
- p.println(" mEnabledSession=" + userData.mEnabledSession);
- final var visibilityStateComputer = userData.mVisibilityStateComputer;
- visibilityStateComputer.dump(pw, " ");
- p.println(" mInFullscreenMode=" + userData.mInFullscreenMode);
}
// Exit here for critical dump, as remaining sections require IPCs to other processes.
@@ -6205,7 +6181,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
return;
}
- p.println(" ");
+ p.println("");
if (client != null) {
pw.flush();
try {
@@ -6217,25 +6193,23 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
p.println("No input method client.");
}
synchronized (ImfLock.class) {
- if (userData.mImeBindingState.mFocusedWindowClient != null
- && client != userData.mImeBindingState.mFocusedWindowClient) {
- p.println(" ");
- p.println("Warning: Current input method client doesn't match the last focused. "
- + "window.");
+ final var focusedWindowClient = userData.mImeBindingState.mFocusedWindowClient;
+ if (focusedWindowClient != null && client != focusedWindowClient) {
+ p.println("");
+ p.println("Warning: Current input method client doesn't match the last focused"
+ + " window.");
p.println("Dumping input method client in the last focused window just in case.");
- p.println(" ");
+ p.println("");
pw.flush();
try {
- TransferPipe.dumpAsync(
- userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd,
- args);
+ TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
} catch (IOException | RemoteException e) {
p.println("Failed to dump input method client in focused window: " + e);
}
}
}
- p.println(" ");
+ p.println("");
if (method != null) {
pw.flush();
try {
@@ -6248,56 +6222,51 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
}
- private void dumpClientStates(Printer p) {
- p.println(" ClientStates:");
+ private void dumpClientController(@NonNull Printer p) {
+ p.println(" mClientController:");
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
- p.println(" " + c + ":");
- p.println(" client=" + c.mClient);
- p.println(" fallbackInputConnection="
- + c.mFallbackInputConnection);
- p.println(" sessionRequested="
- + c.mSessionRequested);
- p.println(" sessionRequestedForAccessibility="
+ p.println(" " + c + ":");
+ p.println(" client=" + c.mClient);
+ p.println(" fallbackInputConnection=" + c.mFallbackInputConnection);
+ p.println(" sessionRequested=" + c.mSessionRequested);
+ p.println(" sessionRequestedForAccessibility="
+ c.mSessionRequestedForAccessibility);
- p.println(" curSession=" + c.mCurSession);
- p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
- p.println(" uid=" + c.mUid);
- p.println(" pid=" + c.mPid);
+ p.println(" curSession=" + c.mCurSession);
+ p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
+ p.println(" uid=" + c.mUid);
+ p.println(" pid=" + c.mPid);
};
synchronized (ImfLock.class) {
mClientController.forAllClients(clientControllerDump);
}
}
- private void dumpUserRepository(Printer p) {
- p.println(" mUserDataRepository=");
+ private void dumpUserRepository(@NonNull Printer p) {
+ p.println(" mUserDataRepository:");
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
- u -> {
- p.println(" mUserId=" + u.mUserId);
- p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get());
- p.println(" hasMainConnection="
- + u.mBindingController.hasMainConnection());
- p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
- p.println(" boundToMethod=" + u.mBoundToMethod);
- p.println(" curClient=" + u.mCurClient);
- if (u.mCurEditorInfo != null) {
- p.println(" curEditorInfo:");
- u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */);
- } else {
- p.println(" curEditorInfo: null");
- }
- p.println(" imeBindingState:");
- u.mImeBindingState.dump(" ", p);
- p.println(" enabledSession=" + u.mEnabledSession);
- p.println(" inFullscreenMode=" + u.mInFullscreenMode);
- p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
- p.println(" switchingController:");
- u.mSwitchingController.dump(p, " ");
- p.println(" mLastEnabledInputMethodsStr="
- + u.mLastEnabledInputMethodsStr);
- };
+ @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump = u -> {
+ p.println(" userId=" + u.mUserId);
+ p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get());
+ p.println(" hasMainConnection=" + u.mBindingController.hasMainConnection());
+ p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
+ p.println(" boundToMethod=" + u.mBoundToMethod);
+ p.println(" curClient=" + u.mCurClient);
+ if (u.mCurEditorInfo != null) {
+ p.println(" curEditorInfo:");
+ u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */);
+ } else {
+ p.println(" curEditorInfo: null");
+ }
+ p.println(" imeBindingState:");
+ u.mImeBindingState.dump(p, " ");
+ p.println(" enabledSession=" + u.mEnabledSession);
+ p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
+ p.println(" switchingController:");
+ u.mSwitchingController.dump(p, " ");
+ p.println(" mLastEnabledInputMethodsStr=" + u.mLastEnabledInputMethodsStr);
+ };
synchronized (ImfLock.class) {
mUserDataRepository.forAllUserData(userDataDump);
}
@@ -6920,6 +6889,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
@BinderThread
@Override
+ public void setHandwritingTouchableRegion(Region region) {
+ synchronized (ImfLock.class) {
+ mImms.mHwController.setHandwritingTouchableRegion(region);
+ }
+ }
+
+ @BinderThread
+ @Override
public void createInputContentUriToken(Uri contentUri, String packageName,
AndroidFuture future /* T=IBinder */) {
@SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index b5ee06863f2b..248fa60f4374 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -287,10 +287,10 @@ final class InputMethodMenuController {
void dump(@NonNull Printer pw, @NonNull String prefix) {
final boolean showing = isisInputMethodPickerShownForTestLocked();
- pw.println(prefix + " isShowing: " + showing);
+ pw.println(prefix + "isShowing: " + showing);
if (showing) {
- pw.println(prefix + " imList: " + mImList);
+ pw.println(prefix + "imList: " + mImList);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index cf2cdc1500f8..9f94905686fe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -30,7 +30,6 @@ import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.os.UserHandle;
import android.provider.Settings;
@@ -48,8 +47,11 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.RecyclerView;
+import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -80,18 +82,27 @@ final class InputMethodMenuControllerNew {
/**
* Shows the Input Method Switcher Menu, with a list of IMEs and their subtypes.
*
- * @param items the list of menu items.
- * @param selectedIndex the index of the menu item that is selected.
- * If no other IMEs are enabled, this index will be out of reach.
- * @param displayId the ID of the display where the menu was requested.
- * @param userId the ID of the user that requested the menu.
+ * @param items the list of input method and subtype items.
+ * @param selectedImeId the ID of the selected input method.
+ * @param selectedSubtypeIndex the index of the selected subtype in the input method's array of
+ * subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if no
+ * subtype is selected.
+ * @param displayId the ID of the display where the menu was requested.
+ * @param userId the ID of the user that requested the menu.
*/
@RequiresPermission(allOf = {INTERACT_ACROSS_USERS, HIDE_OVERLAY_WINDOWS})
- void show(@NonNull List<MenuItem> items, int selectedIndex, int displayId,
- @UserIdInt int userId) {
+ void show(@NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId,
+ int selectedSubtypeIndex, int displayId, @UserIdInt int userId) {
// Hide the menu in case it was already showing.
hide(displayId, userId);
+ final var menuItems = getMenuItems(items);
+ final int selectedIndex = getSelectedIndex(menuItems, selectedImeId, selectedSubtypeIndex);
+ if (selectedIndex == -1) {
+ Slog.w(TAG, "Switching menu shown with no item selected, IME id: " + selectedImeId
+ + ", subtype index: " + selectedSubtypeIndex);
+ }
+
final Context dialogWindowContext = mDialogWindowContext.get(displayId);
final var builder = new AlertDialog.Builder(dialogWindowContext,
com.android.internal.R.style.Theme_DeviceDefault_InputMethodSwitcherDialog);
@@ -104,52 +115,28 @@ final class InputMethodMenuControllerNew {
dialogWindowContext.getText(com.android.internal.R.string.select_input_method));
builder.setView(contentView);
- final DialogInterface.OnClickListener onClickListener = (dialog, which) -> {
- if (which != selectedIndex) {
- final var item = items.get(which);
+ final OnClickListener onClickListener = (item, isSelected) -> {
+ if (!isSelected) {
InputMethodManagerInternal.get()
.switchToInputMethod(item.mImi.getId(), item.mSubtypeIndex, userId);
}
hide(displayId, userId);
};
- final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null;
- final var languageSettingsIntent = selectedImi != null
- ? selectedImi.createImeLanguageSettingsActivityIntent() : null;
- final boolean isDeviceProvisioned = Settings.Global.getInt(
- dialogWindowContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
- 0) != 0;
- final boolean hasLanguageSettingsButton = languageSettingsIntent != null
- && isDeviceProvisioned;
- if (hasLanguageSettingsButton) {
- final View buttonBar = contentView
- .requireViewById(com.android.internal.R.id.button_bar);
- buttonBar.setVisibility(View.VISIBLE);
-
- languageSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final Button languageSettingsButton = contentView
- .requireViewById(com.android.internal.R.id.button1);
- languageSettingsButton.setVisibility(View.VISIBLE);
- languageSettingsButton.setOnClickListener(v -> {
- v.getContext().startActivityAsUser(languageSettingsIntent, UserHandle.of(userId));
- hide(displayId, userId);
- });
- }
-
// Create the current IME subtypes list.
final RecyclerView recyclerView = contentView
.requireViewById(com.android.internal.R.id.list);
- recyclerView.setAdapter(new Adapter(items, selectedIndex, inflater, onClickListener));
+ recyclerView.setAdapter(new Adapter(menuItems, selectedIndex, inflater, onClickListener));
// Scroll to the currently selected IME. This must run after the recycler view is laid out.
recyclerView.post(() -> recyclerView.scrollToPosition(selectedIndex));
- // Indicate that the list can be scrolled.
- recyclerView.setScrollIndicators(
- hasLanguageSettingsButton ? View.SCROLL_INDICATOR_BOTTOM : 0);
// Request focus to enable rotary scrolling on watches.
recyclerView.requestFocus();
+ final var selectedItem = selectedIndex > -1 ? menuItems.get(selectedIndex) : null;
+ updateLanguageSettingsButton(selectedItem, contentView, displayId, userId);
+
builder.setOnCancelListener(dialog -> hide(displayId, userId));
- mMenuItems = items;
+ mMenuItems = menuItems;
mDialog = builder.create();
mDialog.setCanceledOnTouchOutside(true);
final Window w = mDialog.getWindow();
@@ -200,106 +187,319 @@ final class InputMethodMenuControllerNew {
void dump(@NonNull Printer pw, @NonNull String prefix) {
final boolean showing = isShowing();
- pw.println(prefix + " isShowing: " + showing);
+ pw.println(prefix + "isShowing: " + showing);
if (showing) {
- pw.println(prefix + " menuItems: " + mMenuItems);
+ pw.println(prefix + "menuItems: " + mMenuItems);
+ }
+ }
+
+ /**
+ * Creates the list of menu items from the given list of input methods and subtypes. This
+ * handles adding headers and dividers between groups of items from different input methods
+ * as follows:
+ *
+ * <li>If there is only one group, no divider or header will be added.</li>
+ * <li>A divider is added before each group, except the first one.</li>
+ * <li>A header is added before each group (after the divider, if it exists) if the group has
+ * at least two items, or a single item with a subtype name.</li>
+ *
+ * @param items the list of input method and subtype items.
+ */
+ @VisibleForTesting
+ @NonNull
+ static List<MenuItem> getMenuItems(@NonNull List<ImeSubtypeListItem> items) {
+ final var menuItems = new ArrayList<MenuItem>();
+ if (items.isEmpty()) {
+ return menuItems;
+ }
+
+ final var itemsArray = (ArrayList<ImeSubtypeListItem>) items;
+ final int numItems = itemsArray.size();
+ // Initialize to the last IME id to avoid headers if there is only a single IME.
+ String prevImeId = itemsArray.getLast().mImi.getId();
+ boolean firstGroup = true;
+ for (int i = 0; i < numItems; i++) {
+ final var item = itemsArray.get(i);
+
+ final var imeId = item.mImi.getId();
+ final boolean groupChange = !imeId.equals(prevImeId);
+ if (groupChange) {
+ if (!firstGroup) {
+ menuItems.add(DividerItem.getInstance());
+ }
+ // Add a header if we have at least two items, or a single item with a subtype name.
+ final var nextItemId = i + 1 < numItems ? itemsArray.get(i + 1).mImi.getId() : null;
+ final boolean addHeader = item.mSubtypeName != null || imeId.equals(nextItemId);
+ if (addHeader) {
+ menuItems.add(new HeaderItem(item.mImeName));
+ }
+ firstGroup = false;
+ prevImeId = imeId;
+ }
+
+ menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName,
+ item.mImi, item.mSubtypeIndex));
}
+
+ return menuItems;
}
/**
- * Item to be shown in the Input Method Switcher Menu, containing an input method and
- * optionally an input method subtype.
+ * Gets the index of the selected item.
+ *
+ * @param items the list of menu items.
+ * @param selectedImeId the ID of the selected input method.
+ * @param selectedSubtypeIndex the index of the selected subtype in the input method's array of
+ * subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if no
+ * subtype is selected.
+ * @return the index of the selected item, or {@code -1} if no item is selected.
*/
- static class MenuItem {
+ @VisibleForTesting
+ @IntRange(from = -1)
+ static int getSelectedIndex(@NonNull List<MenuItem> items, @Nullable String selectedImeId,
+ int selectedSubtypeIndex) {
+ for (int i = 0; i < items.size(); i++) {
+ final var item = items.get(i);
+ if (item instanceof SubtypeItem subtypeItem) {
+ final var imeId = subtypeItem.mImi.getId();
+ final int subtypeIndex = subtypeItem.mSubtypeIndex;
+ if (imeId.equals(selectedImeId)
+ && ((subtypeIndex == 0 && selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX)
+ || subtypeIndex == NOT_A_SUBTYPE_INDEX
+ || subtypeIndex == selectedSubtypeIndex)) {
+ return i;
+ }
+ }
+ }
+ // Either there is no selected IME, or the selected subtype is enabled but not in the list.
+ // This can happen if an implicit subtype is selected, but we got a list of explicit
+ // subtypes. In this case, the implicit subtype will no longer be included in the list.
+ return -1;
+ }
+
+ /**
+ * Updates the visibility of the Language Settings button to visible if the currently selected
+ * item specifies a (language) settings activity and the device is provisioned. Otherwise,
+ * the button won't be shown.
+ *
+ * @param selectedItem the currently selected item, or {@code null} if no item is selected.
+ * @param view the menu dialog view.
+ * @param displayId the ID of the display where the menu was requested.
+ * @param userId the ID of the user that requested the menu.
+ */
+ @RequiresPermission(allOf = {INTERACT_ACROSS_USERS})
+ private void updateLanguageSettingsButton(@Nullable MenuItem selectedItem, @NonNull View view,
+ int displayId, @UserIdInt int userId) {
+ final var settingsIntent = (selectedItem instanceof SubtypeItem selectedSubtypeItem)
+ ? selectedSubtypeItem.mImi.createImeLanguageSettingsActivityIntent() : null;
+ final boolean isDeviceProvisioned = Settings.Global.getInt(
+ view.getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ 0) != 0;
+ final boolean hasButton = settingsIntent != null && isDeviceProvisioned;
+ final View buttonBar = view.requireViewById(com.android.internal.R.id.button_bar);
+ final Button button = view.requireViewById(com.android.internal.R.id.button1);
+ final RecyclerView recyclerView = view.requireViewById(com.android.internal.R.id.list);
+ if (hasButton) {
+ settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ buttonBar.setVisibility(View.VISIBLE);
+ button.setOnClickListener(v -> {
+ v.getContext().startActivityAsUser(settingsIntent, UserHandle.of(userId));
+ hide(displayId, userId);
+ });
+ // Indicate that the list can be scrolled.
+ recyclerView.setScrollIndicators(View.SCROLL_INDICATOR_BOTTOM);
+ } else {
+ buttonBar.setVisibility(View.GONE);
+ button.setOnClickListener(null);
+ // Remove scroll indicator as there is nothing drawn below the list.
+ recyclerView.setScrollIndicators(0 /* indicators */);
+ }
+ }
+
+ /**
+ * Interface definition for callbacks to be invoked when a {@link SubtypeItem} is clicked.
+ */
+ private interface OnClickListener {
+
+ /**
+ * Called when an item is clicked.
+ *
+ * @param item The item that was clicked.
+ * @param isSelected Whether the item is the currently selected one.
+ */
+ void onClick(@NonNull SubtypeItem item, boolean isSelected);
+ }
+
+ /** Item to be displayed in the menu. */
+ sealed interface MenuItem {}
+
+ /** Subtype item containing an input method and optionally an input method subtype. */
+ static final class SubtypeItem implements MenuItem {
/** The name of the input method. */
@NonNull
- private final CharSequence mImeName;
+ final CharSequence mImeName;
/**
* The name of the input method subtype, or {@code null} if this item doesn't have a
* subtype.
*/
@Nullable
- private final CharSequence mSubtypeName;
+ final CharSequence mSubtypeName;
+
+ /**
+ * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype,
+ * or doesn't specify a layout.
+ */
+ @Nullable
+ private final CharSequence mLayoutName;
/** The info of the input method. */
@NonNull
- private final InputMethodInfo mImi;
+ final InputMethodInfo mImi;
/**
* The index of the subtype in the input method's array of subtypes,
* or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
*/
@IntRange(from = NOT_A_SUBTYPE_INDEX)
- private final int mSubtypeIndex;
-
- /** Whether this item has a group header (only the first item of each input method). */
- private final boolean mHasHeader;
-
- /**
- * Whether this item should has a group divider (same as {@link #mHasHeader},
- * excluding the first IME).
- */
- private final boolean mHasDivider;
+ final int mSubtypeIndex;
- MenuItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
- @NonNull InputMethodInfo imi,
- @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex, boolean hasHeader,
- boolean hasDivider) {
+ SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
+ @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi,
+ @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) {
mImeName = imeName;
mSubtypeName = subtypeName;
+ mLayoutName = layoutName;
mImi = imi;
mSubtypeIndex = subtypeIndex;
- mHasHeader = hasHeader;
- mHasDivider = hasDivider;
}
@Override
public String toString() {
- return "MenuItem{"
+ return "SubtypeItem{"
+ "mImeName=" + mImeName
+ " mSubtypeName=" + mSubtypeName
+ " mSubtypeIndex=" + mSubtypeIndex
- + " mHasHeader=" + mHasHeader
- + " mHasDivider=" + mHasDivider
+ "}";
}
}
- private static class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
+ /** Header item displayed before a group of {@link SubtypeItem} of the same input method. */
+ static final class HeaderItem implements MenuItem {
+
+ /** The header title. */
+ @NonNull
+ final CharSequence mTitle;
+
+ HeaderItem(@NonNull CharSequence title) {
+ mTitle = title;
+ }
+
+ @Override
+ public String toString() {
+ return "HeaderItem{"
+ + "mTitle=" + mTitle
+ + "}";
+ }
+ }
+
+ /** Divider item displayed before a {@link HeaderItem}. */
+ static final class DividerItem implements MenuItem {
+
+ private static DividerItem sInstance;
+
+ /** Gets a singleton instance of DividerItem. */
+ @NonNull
+ static DividerItem getInstance() {
+ if (sInstance == null) {
+ sInstance = new DividerItem();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public String toString() {
+ return "DividerItem{}";
+ }
+ }
+
+ private static final class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+
+ /** View type for unknown item. */
+ private static final int TYPE_UNKNOWN = -1;
+
+ /** View type for {@link SubtypeItem}. */
+ private static final int TYPE_SUBTYPE = 0;
+
+ /** View type for {@link HeaderItem}. */
+ private static final int TYPE_HEADER = 1;
+
+ /** View type for {@link DividerItem}. */
+ private static final int TYPE_DIVIDER = 2;
/** The list of items to show. */
@NonNull
private final List<MenuItem> mItems;
/** The index of the selected item. */
+ @IntRange(from = -1)
private final int mSelectedIndex;
@NonNull
private final LayoutInflater mInflater;
+ /** The listener used to handle clicks on {@link SubtypeViewHolder} items. */
@NonNull
- private final DialogInterface.OnClickListener mOnClickListener;
+ private final OnClickListener mListener;
- Adapter(@NonNull List<MenuItem> items, int selectedIndex,
+ Adapter(@NonNull List<MenuItem> items, @IntRange(from = -1) int selectedIndex,
@NonNull LayoutInflater inflater,
- @NonNull DialogInterface.OnClickListener onClickListener) {
+ @NonNull OnClickListener listener) {
mItems = items;
mSelectedIndex = selectedIndex;
mInflater = inflater;
- mOnClickListener = onClickListener;
+ mListener = listener;
}
@Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final View view = mInflater.inflate(
- com.android.internal.R.layout.input_method_switch_item_new, parent, false);
-
- return new ViewHolder(view, mOnClickListener);
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case TYPE_SUBTYPE -> {
+ final View view = mInflater.inflate(
+ com.android.internal.R.layout.input_method_switch_item_new, parent,
+ false);
+ return new SubtypeViewHolder(view, mListener);
+ }
+ case TYPE_HEADER -> {
+ final View view = mInflater.inflate(
+ com.android.internal.R.layout.input_method_switch_item_header, parent,
+ false);
+ return new HeaderViewHolder(view);
+ }
+ case TYPE_DIVIDER -> {
+ final View view = mInflater.inflate(
+ com.android.internal.R.layout.input_method_switch_item_divider, parent,
+ false);
+ return new DividerViewHolder(view);
+ }
+ default -> throw new IllegalArgumentException("Unknown viewType: " + viewType);
+ }
}
@Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- holder.bind(mItems.get(position), position == mSelectedIndex /* isSelected */);
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ final var item = mItems.get(position);
+ if (holder instanceof SubtypeViewHolder subtypeHolder
+ && item instanceof SubtypeItem subtypeItem) {
+ subtypeHolder.bind(subtypeItem, position == mSelectedIndex /* isSelected */);
+ } else if (holder instanceof HeaderViewHolder headerHolder
+ && item instanceof HeaderItem headerItem) {
+ headerHolder.bind(headerItem);
+ } else if (holder instanceof DividerViewHolder && item instanceof DividerItem) {
+ // Nothing to bind for dividers.
+ return;
+ } else {
+ Slog.w(TAG, "Holder type: " + holder + " doesn't match item type: " + item);
+ }
}
@Override
@@ -307,7 +507,21 @@ final class InputMethodMenuControllerNew {
return mItems.size();
}
- private static class ViewHolder extends RecyclerView.ViewHolder {
+ @Override
+ public int getItemViewType(int position) {
+ final var item = mItems.get(position);
+ if (item instanceof SubtypeItem) {
+ return TYPE_SUBTYPE;
+ } else if (item instanceof HeaderItem) {
+ return TYPE_HEADER;
+ } else if (item instanceof DividerItem) {
+ return TYPE_DIVIDER;
+ } else {
+ return TYPE_UNKNOWN;
+ }
+ }
+
+ private static final class SubtypeViewHolder extends RecyclerView.ViewHolder {
/** The container of the item. */
@NonNull
@@ -315,49 +529,84 @@ final class InputMethodMenuControllerNew {
/** The name of the item. */
@NonNull
private final TextView mName;
+ /** The layout name. */
+ @NonNull
+ private final TextView mLayout;
/** Indicator for the selected status of the item. */
@NonNull
private final ImageView mCheckmark;
- /** The group header optionally drawn above the item. */
- @NonNull
- private final TextView mHeader;
- /** The group divider optionally drawn above the item. */
- @NonNull
- private final View mDivider;
- private ViewHolder(@NonNull View itemView,
- @NonNull DialogInterface.OnClickListener onClickListener) {
+ /** The bound item data, or {@code null} if no item was bound yet. */
+ @Nullable
+ private SubtypeItem mItem;
+ /** Whether this item is the currently selected one. */
+ private boolean mIsSelected;
+
+ SubtypeViewHolder(@NonNull View itemView, @NonNull OnClickListener listener) {
super(itemView);
- mContainer = itemView.requireViewById(com.android.internal.R.id.list_item);
+ mContainer = itemView;
mName = itemView.requireViewById(com.android.internal.R.id.text);
+ mLayout = itemView.requireViewById(com.android.internal.R.id.text2);
mCheckmark = itemView.requireViewById(com.android.internal.R.id.image);
- mHeader = itemView.requireViewById(com.android.internal.R.id.header_text);
- mDivider = itemView.requireViewById(com.android.internal.R.id.divider);
- mContainer.setOnClickListener((v) ->
- onClickListener.onClick(null /* dialog */, getAdapterPosition()));
+ mContainer.setOnClickListener((v) -> {
+ if (mItem != null) {
+ listener.onClick(mItem, mIsSelected);
+ }
+ });
}
/**
* Binds the given item to the current view.
*
* @param item the item to bind.
- * @param isSelected whether this is selected.
+ * @param isSelected whether the item is selected.
*/
- private void bind(@NonNull MenuItem item, boolean isSelected) {
+ void bind(@NonNull SubtypeItem item, boolean isSelected) {
+ mItem = item;
+ mIsSelected = isSelected;
// Use the IME name for subtypes with an empty subtype name.
final var name = TextUtils.isEmpty(item.mSubtypeName)
? item.mImeName : item.mSubtypeName;
mContainer.setActivated(isSelected);
// Activated is the correct state, but we also set selected for accessibility info.
mContainer.setSelected(isSelected);
+ // Trigger the ellipsize marquee behaviour by selecting the name.
mName.setSelected(isSelected);
mName.setText(name);
+ mLayout.setText(item.mLayoutName);
+ mLayout.setVisibility(
+ !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE);
mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE);
- mHeader.setText(item.mImeName);
- mHeader.setVisibility(item.mHasHeader ? View.VISIBLE : View.GONE);
- mDivider.setVisibility(item.mHasDivider ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private static final class HeaderViewHolder extends RecyclerView.ViewHolder {
+
+ /** The title view, only visible if the bound item has a title. */
+ private final TextView mTitle;
+
+ HeaderViewHolder(@NonNull View itemView) {
+ super(itemView);
+
+ mTitle = itemView.requireViewById(com.android.internal.R.id.header_text);
+ }
+
+ /**
+ * Binds the given item to the current view.
+ *
+ * @param item the item to bind.
+ */
+ void bind(@NonNull HeaderItem item) {
+ mTitle.setText(item.mTitle);
+ }
+ }
+
+ private static final class DividerViewHolder extends RecyclerView.ViewHolder {
+
+ DividerViewHolder(@NonNull View itemView) {
+ super(itemView);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 96b3e084d102..51b85e90c447 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -85,6 +85,12 @@ final class InputMethodSubtypeSwitchingController {
public final CharSequence mImeName;
@Nullable
public final CharSequence mSubtypeName;
+ /**
+ * The subtype's layout name, or {@code null} if this item doesn't have a subtype,
+ * or doesn't specify a layout.
+ */
+ @Nullable
+ public final CharSequence mLayoutName;
@NonNull
public final InputMethodInfo mImi;
/**
@@ -96,10 +102,11 @@ final class InputMethodSubtypeSwitchingController {
public final boolean mIsSystemLanguage;
ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
- @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
- @NonNull String systemLocale) {
+ @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex,
+ @Nullable String subtypeLocale, @NonNull String systemLocale) {
mImeName = imeName;
mSubtypeName = subtypeName;
+ mLayoutName = layoutName;
mImi = imi;
mSubtypeIndex = subtypeIndex;
if (TextUtils.isEmpty(subtypeLocale)) {
@@ -252,8 +259,11 @@ final class InputMethodSubtypeSwitchingController {
subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
.getDisplayName(userAwareContext, imi.getPackageName(),
imi.getServiceInfo().applicationInfo);
- imList.add(new ImeSubtypeListItem(imeLabel,
- subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+ : subtype.getLayoutDisplayName(userAwareContext,
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+ imi, j, subtype.getLocale(), mSystemLocaleStr));
// Removing this subtype from enabledSubtypeSet because we no
// longer need to add an entry of this subtype to imList to avoid
@@ -262,8 +272,8 @@ final class InputMethodSubtypeSwitchingController {
}
}
} else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
- mSystemLocaleStr));
+ imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+ null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
}
}
Collections.sort(imList);
@@ -311,13 +321,16 @@ final class InputMethodSubtypeSwitchingController {
subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
.getDisplayName(userAwareContext, imi.getPackageName(),
imi.getServiceInfo().applicationInfo);
- imList.add(new ImeSubtypeListItem(imeLabel,
- subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+ : subtype.getLayoutDisplayName(userAwareContext,
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+ imi, j, subtype.getLocale(), mSystemLocaleStr));
}
}
} else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
- mSystemLocaleStr));
+ imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+ null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
}
}
return imList;
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
index 3023603dc437..84456327526a 100644
--- a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -18,6 +18,7 @@ package com.android.server.inputmethod;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.SystemClock;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -62,6 +63,8 @@ final class SoftInputShowHideHistory {
final String mImeTargetNameFromWm;
@Nullable
final String mImeSurfaceParentName;
+ @UserIdInt
+ final int mImeUserId;
Entry(ClientState client, EditorInfo editorInfo,
String focusedWindowName,
@@ -69,7 +72,7 @@ final class SoftInputShowHideHistory {
@SoftInputShowHideReason int reason,
boolean inFullscreenMode, String requestWindowName,
@Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
+ @Nullable String imeSurfaceParentName, @UserIdInt int imeUserId) {
mClientState = client;
mEditorInfo = editorInfo;
mFocusedWindowName = focusedWindowName;
@@ -82,6 +85,7 @@ final class SoftInputShowHideHistory {
mImeControlTargetName = imeControlTargetName;
mImeTargetNameFromWm = imeTargetName;
mImeSurfaceParentName = imeSurfaceParentName;
+ mImeUserId = imeUserId;
}
}
@@ -102,7 +106,8 @@ final class SoftInputShowHideHistory {
continue;
}
pw.print(prefix);
- pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+ pw.println("SoftInputShowHide[" + entry.mImeUserId + "] #"
+ + entry.mSequenceNumber + ":");
pw.print(prefix);
pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 214aa1d904fa..49d4332d9e2a 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -394,6 +394,7 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
flags),
this::offload).get();
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index cc0a7340494c..0944a5470ba0 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -219,7 +219,7 @@ public class LocaleManagerService extends SystemService {
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new LocaleManagerShellCommand(mBinderService))
+ (new LocaleManagerShellCommand(mBinderService, mContext))
.exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
index 09f2ffa35da5..926b64744a76 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
@@ -19,6 +19,8 @@ package com.android.server.locales;
import android.app.ActivityManager;
import android.app.ILocaleManager;
import android.app.LocaleConfig;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -31,9 +33,11 @@ import java.io.PrintWriter;
*/
public class LocaleManagerShellCommand extends ShellCommand {
private final ILocaleManager mBinderService;
+ private final Context mContext;
- LocaleManagerShellCommand(ILocaleManager localeManager) {
+ LocaleManagerShellCommand(ILocaleManager localeManager, Context context) {
mBinderService = localeManager;
+ mContext = context;
}
@Override
public int onCommand(String cmd) {
@@ -49,6 +53,8 @@ public class LocaleManagerShellCommand extends ShellCommand {
return runSetAppOverrideLocaleConfig();
case "get-app-localeconfig":
return runGetAppOverrideLocaleConfig();
+ case "get-app-localeconfig-ignore-override":
+ return runGetAppLocaleConfigIgnoreOverride();
default: {
return handleDefaultCommands(cmd);
}
@@ -261,6 +267,55 @@ public class LocaleManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runGetAppLocaleConfigIgnoreOverride() {
+ String packageName = getNextArg();
+ final PrintWriter err = getErrPrintWriter();
+
+ if (packageName != null) {
+ int userId = ActivityManager.getCurrentUser();
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ if ("--user".equals(option)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ } while (true);
+ LocaleConfig resLocaleConfig = null;
+ try {
+ resLocaleConfig = LocaleConfig.fromContextIgnoringOverride(
+ mContext.createPackageContextAsUser(packageName, /* flags= */ 0,
+ UserHandle.of(userId)));
+ } catch (PackageManager.NameNotFoundException e) {
+ err.println("Unknown package name " + packageName + " for user " + userId);
+ return -1;
+ }
+ if (resLocaleConfig == null) {
+ getOutPrintWriter().println(
+ "LocaleConfig for " + packageName + " for user " + userId + " is null");
+ } else {
+ LocaleList locales = resLocaleConfig.getSupportedLocales();
+ if (locales == null) {
+ getOutPrintWriter().println(
+ "Locales within the LocaleConfig for " + packageName + " for user "
+ + userId + " are null");
+ } else {
+ getOutPrintWriter().println(
+ "Locales within the LocaleConfig for " + packageName + " for user "
+ + userId + " are [" + locales.toLanguageTags() + "]");
+ }
+ }
+ } else {
+ err.println("Error: no package specified");
+ return -1;
+ }
+ return 0;
+ }
+
private LocaleList parseOverrideLocales() {
String locales = getNextArg();
if (locales == null) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2e167efc4d81..6053557f575c 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -108,6 +108,7 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssConfiguration;
@@ -147,6 +148,7 @@ import com.android.server.location.provider.PassiveLocationProviderManager;
import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyGeocodeProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -260,6 +262,11 @@ public class LocationManagerService extends ILocationManager.Stub implements
private volatile @Nullable GnssManagerService mGnssManagerService = null;
private ProxyGeocodeProvider mGeocodeProvider;
+ private @Nullable ProxyPopulationDensityProvider mPopulationDensityProvider = null;
+
+ // A cache for population density lookups. Used if density-based coarse locations are enabled.
+ private @Nullable LocationFudgerCache mLocationFudgerCache = null;
+
private final Object mDeprecatedGnssBatchingLock = new Object();
@GuardedBy("mDeprecatedGnssBatchingLock")
private @Nullable ILocationListener mDeprecatedGnssBatchingListener;
@@ -392,6 +399,25 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
+ @VisibleForTesting
+ protected void setProxyPopulationDensityProvider(ProxyPopulationDensityProvider provider) {
+ if (Flags.populationDensityProvider()) {
+ mPopulationDensityProvider = provider;
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudgerCache = cache;
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.setLocationFudgerCache(cache);
+ }
+ }
+
private void removeLocationProviderManager(LocationProviderManager manager) {
synchronized (mProviderManagers) {
boolean removed = mProviderManagers.remove(manager);
@@ -510,6 +536,17 @@ public class LocationManagerService extends ILocationManager.Stub implements
Log.e(TAG, "no geocoder provider found");
}
+ if (Flags.populationDensityProvider()) {
+ setProxyPopulationDensityProvider(
+ ProxyPopulationDensityProvider.createAndRegister(mContext));
+ if (mPopulationDensityProvider == null) {
+ Log.e(TAG, "no population density provider found");
+ }
+ }
+ if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) {
+ setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider));
+ }
+
// bind to hardware activity recognition
HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy =
HardwareActivityRecognitionProxy.createAndRegister(mContext);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 91a4d6f1707f..556cc03b3abd 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -500,7 +500,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
checkNanoappPermsAsync();
}
- if (!Flags.reliableMessageImplementation() || transactionCallback == null) {
+ if (transactionCallback == null) {
try {
result = mContextHubProxy.sendMessageToContextHub(mHostEndPointId,
mAttachedContextHubInfo.getId(), message);
@@ -671,10 +671,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
.putExtra(ContextHubManager.EXTRA_MESSAGE, message);
Consumer<Byte> onFinishedCallback = (Byte error) ->
sendMessageDeliveryStatusToContextHub(message.getMessageSequenceNumber(), error);
- return sendPendingIntent(supplier, nanoAppId,
- Flags.reliableMessageImplementation() && message.isReliable()
- ? onFinishedCallback
- : null);
+ return sendPendingIntent(
+ supplier, nanoAppId, message.isReliable() ? onFinishedCallback : null);
}
/**
@@ -764,6 +762,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
boolean hasPermissions(List<String> permissions) {
for (String permission : permissions) {
if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
+ Log.e(TAG, "no permission for " + permission);
return false;
}
}
@@ -919,6 +918,14 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
}
}
if (curAuthState != newAuthState) {
+ if (newAuthState == AUTHORIZATION_DENIED
+ || newAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+ Log.e(TAG, "updateNanoAppAuthState auth error: "
+ + Long.toHexString(nanoAppId) + ", "
+ + nanoappPermissions + ", "
+ + gracePeriodExpired + ", "
+ + forceDenied);
+ }
// Don't send the callback in the synchronized block or it could end up in a deadlock.
sendAuthStateCallback(nanoAppId, newAuthState);
}
@@ -1275,10 +1282,6 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
}
private void sendMessageDeliveryStatusToContextHub(int messageSequenceNumber, byte errorCode) {
- if (!Flags.reliableMessageImplementation()) {
- return;
- }
-
MessageDeliveryStatus status = new MessageDeliveryStatus();
status.messageSequenceNumber = messageSequenceNumber;
status.errorCode = errorCode;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 7285151ee598..5248a051404d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -236,16 +236,17 @@ import java.util.function.Consumer;
}
if (message.isBroadcastMessage()) {
- if (Flags.reliableMessageImplementation() && message.isReliable()) {
- Log.e(TAG, "Received reliable broadcast message from " + message.getNanoAppId());
+ if (message.isReliable()) {
+ Log.e(TAG, "Received reliable broadcast message from 0x"
+ + Long.toHexString(message.getNanoAppId()));
return ErrorCode.PERMANENT_ERROR;
}
// Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
// requirements.
if (!messagePermissions.isEmpty()) {
- Log.e(TAG, "Received broadcast message with permissions from "
- + message.getNanoAppId());
+ Log.e(TAG, "Received broadcast message with permissions from 0x"
+ + Long.toHexString(message.getNanoAppId()));
return ErrorCode.PERMANENT_ERROR;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
new file mode 100644
index 000000000000..ef73463122ff
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubMessage;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that represents a broker for the endpoint registered by the client app. This class
+ * manages direct IContextHubEndpoint/IContextHubEndpointCallback API/callback calls.
+ *
+ * @hide
+ */
+public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ implements IBinder.DeathRecipient {
+ private static final String TAG = "ContextHubEndpointBroker";
+
+ /** The context of the service. */
+ private final Context mContext;
+
+ /** The proxy to talk to the Context Hub HAL. */
+ private final IContextHubWrapper mContextHubProxy;
+
+ /** The manager that registered this endpoint. */
+ private final ContextHubEndpointManager mEndpointManager;
+
+ /** Metadata about this endpoint (app-facing container). */
+ private final HubEndpointInfo mEndpointInfo;
+
+ /** Metadata about this endpoint (HAL-facing container). */
+ private final EndpointInfo mHalEndpointInfo;
+
+ /** The remote callback interface for this endpoint. */
+ private final IContextHubEndpointCallback mContextHubEndpointCallback;
+
+ /** True if this endpoint is registered with the service. */
+ private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
+
+ private final Object mOpenSessionLock = new Object();
+
+ /** The set of session IDs that are pending remote acceptance */
+ @GuardedBy("mOpenSessionLock")
+ private final Set<Integer> mPendingSessionIds = new HashSet<>();
+
+ /** The set of session IDs that are actively enabled by this endpoint */
+ @GuardedBy("mOpenSessionLock")
+ private final Set<Integer> mActiveSessionIds = new HashSet<>();
+
+ /** The set of session IDs that are actively enabled by the remote endpoint */
+ @GuardedBy("mOpenSessionLock")
+ private final Set<Integer> mActiveRemoteSessionIds = new HashSet<>();
+
+ /* package */ ContextHubEndpointBroker(
+ Context context,
+ IContextHubWrapper contextHubProxy,
+ ContextHubEndpointManager endpointManager,
+ EndpointInfo halEndpointInfo,
+ IContextHubEndpointCallback callback) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mEndpointManager = endpointManager;
+ mEndpointInfo = new HubEndpointInfo(halEndpointInfo);
+ mHalEndpointInfo = halEndpointInfo;
+ mContextHubEndpointCallback = callback;
+ }
+
+ @Override
+ public HubEndpointInfo getAssignedHubEndpointInfo() {
+ return mEndpointInfo;
+ }
+
+ @Override
+ public int openSession(HubEndpointInfo destination, String serviceDescriptor)
+ throws RemoteException {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ int sessionId = mEndpointManager.reserveSessionId();
+ EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
+
+ synchronized (mOpenSessionLock) {
+ try {
+ mPendingSessionIds.add(sessionId);
+ mContextHubProxy.openEndpointSession(
+ sessionId,
+ halEndpointInfo.id,
+ mHalEndpointInfo.id,
+ serviceDescriptor);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling HAL openEndpointSession", e);
+ mPendingSessionIds.remove(sessionId);
+ mEndpointManager.returnSessionId(sessionId);
+ throw e;
+ }
+
+ return sessionId;
+ }
+ }
+
+ @Override
+ public void closeSession(int sessionId, int reason) throws RemoteException {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ try {
+ mContextHubProxy.closeEndpointSession(sessionId, (byte) reason);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
+ throw e;
+ }
+ }
+
+ @Override
+ public void unregister() {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ mIsRegistered.set(false);
+ try {
+ mContextHubProxy.unregisterEndpoint(mHalEndpointInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
+ }
+ synchronized (mOpenSessionLock) {
+ for (int id : mPendingSessionIds) {
+ mEndpointManager.returnSessionId(id);
+ }
+ for (int id : mActiveSessionIds) {
+ mEndpointManager.returnSessionId(id);
+ }
+ mPendingSessionIds.clear();
+ mActiveSessionIds.clear();
+ mActiveRemoteSessionIds.clear();
+ }
+ mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
+ }
+
+ @Override
+ public void openSessionRequestComplete(int sessionId) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ synchronized (mOpenSessionLock) {
+ try {
+ mContextHubProxy.endpointSessionOpenComplete(sessionId);
+ mActiveRemoteSessionIds.add(sessionId);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
+ }
+ }
+ }
+
+ @Override
+ public void sendMessage(
+ int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
+ // TODO(b/381102453): Implement this
+ }
+
+ @Override
+ public void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode) {
+ // TODO(b/381102453): Implement this
+ }
+
+ /** Invoked when the underlying binder of this broker has died at the client process. */
+ @Override
+ public void binderDied() {
+ if (mIsRegistered.get()) {
+ unregister();
+ }
+ }
+
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ if (mContextHubEndpointCallback != null) {
+ mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
+ }
+ }
+
+ /* package */ void onEndpointSessionOpenRequest(
+ int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
+ if (mContextHubEndpointCallback != null) {
+ try {
+ mContextHubEndpointCallback.onSessionOpenRequest(
+ sessionId, initiator, serviceDescriptor);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onSessionOpenRequest", e);
+ }
+ }
+ }
+
+ /* package */ void onCloseEndpointSession(int sessionId, byte reason) {
+ synchronized (mOpenSessionLock) {
+ mPendingSessionIds.remove(sessionId);
+ mActiveSessionIds.remove(sessionId);
+ mActiveRemoteSessionIds.remove(sessionId);
+ }
+ if (mContextHubEndpointCallback != null) {
+ try {
+ mContextHubEndpointCallback.onSessionClosed(sessionId, reason);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onSessionClosed", e);
+ }
+ }
+ }
+
+ /* package */ void onEndpointSessionOpenComplete(int sessionId) {
+ synchronized (mOpenSessionLock) {
+ mPendingSessionIds.remove(sessionId);
+ mActiveSessionIds.add(sessionId);
+ }
+ if (mContextHubEndpointCallback != null) {
+ try {
+ mContextHubEndpointCallback.onSessionOpenComplete(sessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onSessionClosed", e);
+ }
+ }
+ }
+
+ /* package */ boolean hasSessionId(int sessionId) {
+ synchronized (mOpenSessionLock) {
+ return mPendingSessionIds.contains(sessionId)
+ || mActiveSessionIds.contains(sessionId)
+ || mActiveRemoteSessionIds.contains(sessionId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
new file mode 100644
index 000000000000..155a92a21368
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubEndpointManager
+ implements ContextHubHalEndpointCallback.IEndpointSessionCallback {
+ private static final String TAG = "ContextHubEndpointManager";
+
+ /** The hub ID of the Context Hub Service. */
+ private static final long SERVICE_HUB_ID = 0x416e64726f696400L;
+
+ /** The range of session IDs to use for endpoints */
+ private static final int SERVICE_SESSION_RANGE = 1024;
+
+ /** The length of the array that should be returned by HAL requestSessionIdRange */
+ private static final int SERVICE_SESSION_RANGE_LENGTH = 2;
+
+ /** The context of the service. */
+ private final Context mContext;
+
+ /** The proxy to talk to the Context Hub. */
+ private final IContextHubWrapper mContextHubProxy;
+
+ private final HubInfoRegistry mHubInfoRegistry;
+
+ /** A map of endpoint IDs to brokers currently registered. */
+ private final Map<Long, ContextHubEndpointBroker> mEndpointMap = new ConcurrentHashMap<>();
+
+ /** Variables for managing endpoint ID creation */
+ private final Object mEndpointLock = new Object();
+
+ /**
+ * The next available endpoint ID to register. Per EndpointId.aidl definition, dynamic
+ * endpoint IDs must have the left-most bit as 1, and the values 0/-1 are invalid.
+ */
+ @GuardedBy("mEndpointLock")
+ private long mNextEndpointId = -2;
+
+ /** The minimum session ID reservable by endpoints (retrieved from HAL) */
+ private final int mMinSessionId;
+
+ /** The minimum session ID reservable by endpoints (retrieved from HAL) */
+ private final int mMaxSessionId;
+
+ /** Variables for managing session ID creation */
+ private final Object mSessionIdLock = new Object();
+
+ /** A set of session IDs that have been reserved by an endpoint. */
+ @GuardedBy("mSessionIdLock")
+ private final Set<Integer> mReservedSessionIds =
+ Collections.newSetFromMap(new HashMap<Integer, Boolean>());
+
+ @GuardedBy("mSessionIdLock")
+ private int mNextSessionId = 0;
+
+ /** Initialized to true if all initialization in the constructor succeeds. */
+ private final boolean mSessionIdsValid;
+
+ /* package */ ContextHubEndpointManager(
+ Context context, IContextHubWrapper contextHubProxy, HubInfoRegistry hubInfoRegistry) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mHubInfoRegistry = hubInfoRegistry;
+ int[] range = null;
+ try {
+ range = mContextHubProxy.requestSessionIdRange(SERVICE_SESSION_RANGE);
+ if (range != null && range.length < SERVICE_SESSION_RANGE_LENGTH) {
+ Log.e(TAG, "Invalid session ID range: range array size = " + range.length);
+ range = null;
+ }
+ } catch (RemoteException | IllegalArgumentException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception while calling HAL requestSessionIdRange", e);
+ }
+
+ if (range == null) {
+ mMinSessionId = -1;
+ mMaxSessionId = -1;
+ mSessionIdsValid = false;
+ } else {
+ mMinSessionId = range[0];
+ mMaxSessionId = range[1];
+ if (!isSessionIdRangeValid(mMinSessionId, mMaxSessionId)) {
+ Log.e(
+ TAG,
+ "Invalid session ID range: max=" + mMaxSessionId + " min=" + mMinSessionId);
+ mSessionIdsValid = false;
+ } else {
+ mNextSessionId = mMinSessionId;
+ mSessionIdsValid = true;
+ }
+ }
+ }
+
+ /**
+ * Registers a new endpoint with the service.
+ *
+ * @param pendingEndpointInfo the object describing the endpoint being registered
+ * @param callback the callback interface of the endpoint to register
+ * @return the endpoint interface
+ * @throws IllegalStateException if max number of endpoints have already registered
+ */
+ /* package */ IContextHubEndpoint registerEndpoint(
+ HubEndpointInfo pendingEndpointInfo, IContextHubEndpointCallback callback)
+ throws RemoteException {
+ if (!mSessionIdsValid) {
+ throw new IllegalStateException("ContextHubEndpointManager failed to initialize");
+ }
+ ContextHubEndpointBroker broker;
+ long endpointId = getNewEndpointId();
+ EndpointInfo halEndpointInfo =
+ ContextHubServiceUtil.createHalEndpointInfo(
+ pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
+ try {
+ mContextHubProxy.registerEndpoint(halEndpointInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
+ throw e;
+ }
+ broker =
+ new ContextHubEndpointBroker(
+ mContext,
+ mContextHubProxy,
+ this /* endpointManager */,
+ halEndpointInfo,
+ callback);
+ mEndpointMap.put(endpointId, broker);
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null
+ Log.e(TAG, "Failed to attach death recipient to client", e);
+ broker.unregister();
+ return null;
+ }
+
+ Log.d(TAG, "Registered endpoint with ID = " + endpointId);
+ return IContextHubEndpoint.Stub.asInterface(broker);
+ }
+
+ /**
+ * Reserves an available session ID for an endpoint.
+ *
+ * @throws IllegalStateException if no session ID was available.
+ * @return The reserved session ID.
+ */
+ /* package */ int reserveSessionId() {
+ int id = -1;
+ synchronized (mSessionIdLock) {
+ final int maxCapacity = mMaxSessionId - mMinSessionId + 1;
+ if (mReservedSessionIds.size() >= maxCapacity) {
+ throw new IllegalStateException("Too many sessions reserved");
+ }
+
+ id = mNextSessionId;
+ for (int i = mMinSessionId; i <= mMaxSessionId; i++) {
+ if (!mReservedSessionIds.contains(id)) {
+ mNextSessionId = (id == mMaxSessionId) ? mMinSessionId : id + 1;
+ break;
+ }
+
+ id = (id == mMaxSessionId) ? mMinSessionId : id + 1;
+ }
+
+ mReservedSessionIds.add(id);
+ }
+ return id;
+ }
+
+ /** Returns a previously reserved session ID through {@link #reserveSessionId()}. */
+ /* package */ void returnSessionId(int sessionId) {
+ synchronized (mSessionIdLock) {
+ mReservedSessionIds.remove(sessionId);
+ }
+ }
+
+ /**
+ * Unregisters an endpoint given its ID.
+ *
+ * @param endpointId The ID of the endpoint to unregister.
+ */
+ /* package */ void unregisterEndpoint(long endpointId) {
+ mEndpointMap.remove(endpointId);
+ }
+
+ @Override
+ public void onEndpointSessionOpenRequest(
+ int sessionId,
+ HubEndpointInfo.HubEndpointIdentifier destination,
+ HubEndpointInfo.HubEndpointIdentifier initiator,
+ String serviceDescriptor) {
+ if (destination.getHub() != SERVICE_HUB_ID) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: invalid destination hub ID: "
+ + destination.getHub());
+ return;
+ }
+ ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint());
+ if (broker == null) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: unknown destination endpoint ID: "
+ + destination.getEndpoint());
+ return;
+ }
+ HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator);
+ if (initiatorInfo == null) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
+ + initiator.getEndpoint());
+ return;
+ }
+ broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+ }
+
+ @Override
+ public void onCloseEndpointSession(int sessionId, byte reason) {
+ boolean callbackInvoked = false;
+ for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+ if (broker.hasSessionId(sessionId)) {
+ broker.onCloseEndpointSession(sessionId, reason);
+ callbackInvoked = true;
+ break;
+ }
+ }
+
+ if (!callbackInvoked) {
+ Log.w(TAG, "onCloseEndpointSession: unknown session ID " + sessionId);
+ }
+ }
+
+ @Override
+ public void onEndpointSessionOpenComplete(int sessionId) {
+ boolean callbackInvoked = false;
+ for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+ if (broker.hasSessionId(sessionId)) {
+ broker.onEndpointSessionOpenComplete(sessionId);
+ callbackInvoked = true;
+ break;
+ }
+ }
+
+ if (!callbackInvoked) {
+ Log.w(TAG, "onEndpointSessionOpenComplete: unknown session ID " + sessionId);
+ }
+ }
+
+ /** @return an available endpoint ID */
+ private long getNewEndpointId() {
+ synchronized (mEndpointLock) {
+ if (mNextEndpointId >= 0) {
+ throw new IllegalStateException("Too many endpoints connected");
+ }
+ return mNextEndpointId--;
+ }
+ }
+
+ /**
+ * @return true if the provided session ID range is valid
+ */
+ private boolean isSessionIdRangeValid(int minId, int maxId) {
+ return (minId <= maxId) && (minId >= 0) && (maxId >= 0);
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
index e1b1416169cd..53a02cd1b677 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -333,10 +333,6 @@ public class ContextHubEventLogger {
*/
public synchronized void logReliableMessageToNanoappStatus(
int messageSequenceNumber, byte errorCode) {
- if (!Flags.reliableMessage()) {
- return;
- }
-
for (NanoappMessageEvent event : mMessageToNanoappQueue) {
if (event.message.isReliable()
&& event.message.getMessageSequenceNumber()
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
new file mode 100644
index 000000000000..9d52c6a020f4
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location.contexthub;
+
+import android.hardware.contexthub.EndpointId;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IEndpointCallback;
+import android.hardware.contexthub.Message;
+import android.hardware.contexthub.MessageDeliveryStatus;
+import android.os.RemoteException;
+
+/** IEndpointCallback implementation. */
+public class ContextHubHalEndpointCallback
+ extends android.hardware.contexthub.IEndpointCallback.Stub {
+ private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
+ private final IEndpointSessionCallback mEndpointSessionCallback;
+
+ /** Interface for listening for endpoint start and stop events. */
+ public interface IEndpointLifecycleCallback {
+ /** Called when a batch of endpoints started. */
+ void onEndpointStarted(HubEndpointInfo[] endpointInfos);
+
+ /** Called when a batch of endpoints stopped. */
+ void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason);
+ }
+
+ /** Interface for listening for endpoint session events. */
+ public interface IEndpointSessionCallback {
+ /** Called when an endpoint session open is requested by the HAL. */
+ void onEndpointSessionOpenRequest(
+ int sessionId,
+ HubEndpointInfo.HubEndpointIdentifier destinationId,
+ HubEndpointInfo.HubEndpointIdentifier initiatorId,
+ String serviceDescriptor);
+
+ /** Called when a endpoint close session is completed. */
+ void onCloseEndpointSession(int sessionId, byte reason);
+
+ /** Called when a requested endpoint open session is completed */
+ void onEndpointSessionOpenComplete(int sessionId);
+ }
+
+ ContextHubHalEndpointCallback(
+ IEndpointLifecycleCallback endpointLifecycleCallback,
+ IEndpointSessionCallback endpointSessionCallback) {
+ mEndpointLifecycleCallback = endpointLifecycleCallback;
+ mEndpointSessionCallback = endpointSessionCallback;
+ }
+
+ @Override
+ public void onEndpointStarted(android.hardware.contexthub.EndpointInfo[] halEndpointInfos)
+ throws RemoteException {
+ if (halEndpointInfos.length == 0) {
+ return;
+ }
+ HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length];
+ for (int i = 0; i < halEndpointInfos.length; i++) {
+ endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]);
+ }
+ mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
+ }
+
+ @Override
+ public void onEndpointStopped(EndpointId[] halEndpointIds, byte reason) throws RemoteException {
+ HubEndpointInfo.HubEndpointIdentifier[] endpointIds =
+ new HubEndpointInfo.HubEndpointIdentifier[halEndpointIds.length];
+ for (int i = 0; i < halEndpointIds.length; i++) {
+ endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
+ }
+ mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
+ }
+
+ @Override
+ public void onMessageReceived(int i, Message message) throws RemoteException {}
+
+ @Override
+ public void onMessageDeliveryStatusReceived(int i, MessageDeliveryStatus messageDeliveryStatus)
+ throws RemoteException {}
+
+ @Override
+ public void onEndpointSessionOpenRequest(
+ int i, EndpointId destination, EndpointId initiator, String s) throws RemoteException {
+ HubEndpointInfo.HubEndpointIdentifier destinationId =
+ new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id);
+ HubEndpointInfo.HubEndpointIdentifier initiatorId =
+ new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id);
+ mEndpointSessionCallback.onEndpointSessionOpenRequest(i, destinationId, initiatorId, s);
+ }
+
+ @Override
+ public void onCloseEndpointSession(int i, byte b) throws RemoteException {
+ mEndpointSessionCallback.onCloseEndpointSession(i, b);
+ }
+
+ @Override
+ public void onEndpointSessionOpenComplete(int i) throws RemoteException {
+ mEndpointSessionCallback.onEndpointSessionOpenComplete(i);
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return IEndpointCallback.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return IEndpointCallback.HASH;
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 3f4a9bb4d864..d916eda693d8 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -31,10 +31,15 @@ import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
import android.hardware.location.IContextHubCallback;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
@@ -57,6 +62,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.proto.ProtoOutputStream;
@@ -134,6 +140,9 @@ public class ContextHubService extends IContextHubService.Stub {
private Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
private List<String> mSupportedContextHubPerms;
private List<ContextHubInfo> mContextHubInfoList;
+
+ @Nullable private final HubInfoRegistry mHubInfoRegistry;
+
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
@@ -146,6 +155,9 @@ public class ContextHubService extends IContextHubService.Stub {
// The manager for sending messages to/from clients
private ContextHubClientManager mClientManager;
+ // Manages endpoint and its sessions between apps and HAL
+ private ContextHubEndpointManager mEndpointManager;
+
// The default client for old API clients
private Map<Integer, IContextHubClient> mDefaultClientMap;
@@ -228,9 +240,8 @@ public class ContextHubService extends IContextHubService.Stub {
// Only process the message normally if not using test mode manager or if
// the test mode manager call returned false as this indicates it did not
// process the message.
- boolean useTestModeManager = Flags.reliableMessageImplementation()
- && Flags.reliableMessageTestModeBehavior()
- && mIsTestModeEnabled.get();
+ boolean useTestModeManager =
+ Flags.reliableMessageTestModeBehavior() && mIsTestModeEnabled.get();
if (!useTestModeManager
|| !mTestModeManager.handleNanoappMessage(() -> {
handleClientMessageCallback(mContextHubId, hostEndpointId,
@@ -245,6 +256,7 @@ public class ContextHubService extends IContextHubService.Stub {
public void handleServiceRestart() {
Log.i(TAG, "Recovering from Context Hub HAL restart...");
initExistingCallbacks();
+ mHubInfoRegistry.onHalRestart();
resetSettings();
if (Flags.reconnectHostEndpointsAfterHalRestart()) {
mClientManager.forEachClientOfHub(mContextHubId,
@@ -310,11 +322,34 @@ public class ContextHubService extends IContextHubService.Stub {
mContext = context;
long startTimeNs = SystemClock.elapsedRealtimeNanos();
mContextHubWrapper = contextHubWrapper;
+
if (!initContextHubServiceState(startTimeNs)) {
Log.e(TAG, "Failed to initialize the Context Hub Service");
+ mHubInfoRegistry = null;
return;
}
+
+ if (Flags.offloadApi() && Flags.offloadImplementation()) {
+ HubInfoRegistry registry;
+ try {
+ registry = new HubInfoRegistry(mContextHubWrapper);
+ mEndpointManager =
+ new ContextHubEndpointManager(mContext, mContextHubWrapper, registry);
+ Log.i(TAG, "Enabling generic offload API");
+ } catch (UnsupportedOperationException e) {
+ mEndpointManager = null;
+ registry = null;
+ Log.w(TAG, "Generic offload API not supported, disabling");
+ }
+ mHubInfoRegistry = registry;
+ } else {
+ mHubInfoRegistry = null;
+ mEndpointManager = null;
+ Log.i(TAG, "Disabling generic offload API");
+ }
+
initDefaultClientMap();
+ initEndpointCallback();
initLocationSettingNotifications();
initWifiSettingNotifications();
@@ -428,7 +463,7 @@ public class ContextHubService extends IContextHubService.Stub {
Pair<List<ContextHubInfo>, List<String>> hubInfo;
try {
- hubInfo = mContextHubWrapper.getHubs();
+ hubInfo = mContextHubWrapper.getContextHubs();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList());
@@ -444,8 +479,17 @@ public class ContextHubService extends IContextHubService.Stub {
mSupportedContextHubPerms = hubInfo.second;
mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
- mTransactionManager = new ContextHubTransactionManager(
- mContextHubWrapper, mClientManager, mNanoAppStateManager);
+
+ if (Flags.reduceLockingContextHubTransactionManager()) {
+ mTransactionManager =
+ new ContextHubTransactionManager(
+ mContextHubWrapper, mClientManager, mNanoAppStateManager);
+ } else {
+ mTransactionManager =
+ new ContextHubTransactionManagerOld(
+ mContextHubWrapper, mClientManager, mNanoAppStateManager);
+ }
+
mSensorPrivacyManagerInternal =
LocalServices.getService(SensorPrivacyManagerInternal.class);
return true;
@@ -484,6 +528,18 @@ public class ContextHubService extends IContextHubService.Stub {
mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
}
+ private void initEndpointCallback() {
+ if (mHubInfoRegistry == null) {
+ return;
+ }
+ try {
+ mContextHubWrapper.registerEndpointCallback(
+ new ContextHubHalEndpointCallback(mHubInfoRegistry, mEndpointManager));
+ } catch (RemoteException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while registering IEndpointCallback", e);
+ }
+ }
+
/**
* Initializes existing callbacks with the mContextHubWrapper for every context hub
*/
@@ -705,6 +761,84 @@ public class ContextHubService extends IContextHubService.Stub {
return mContextHubInfoList;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public List<HubInfo> getHubs() throws RemoteException {
+ super.getHubs_enforcePermission();
+ if (mHubInfoRegistry == null) {
+ return Collections.emptyList();
+ }
+ return mHubInfoRegistry.getHubs();
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public List<HubEndpointInfo> findEndpoints(long endpointId) {
+ super.findEndpoints_enforcePermission();
+ if (mHubInfoRegistry == null) {
+ return Collections.emptyList();
+ }
+ return mHubInfoRegistry.findEndpoints(endpointId);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public List<HubEndpointInfo> findEndpointsWithService(String serviceDescriptor) {
+ super.findEndpointsWithService_enforcePermission();
+ if (mHubInfoRegistry == null) {
+ return Collections.emptyList();
+ }
+ return mHubInfoRegistry.findEndpointsWithService(serviceDescriptor);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public IContextHubEndpoint registerEndpoint(
+ HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback)
+ throws RemoteException {
+ super.registerEndpoint_enforcePermission();
+ if (mEndpointManager == null) {
+ Log.e(TAG, "Endpoint manager failed to initialize");
+ throw new UnsupportedOperationException("Endpoint registration is not supported");
+ }
+ return mEndpointManager.registerEndpoint(pendingHubEndpointInfo, callback);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void registerEndpointDiscoveryCallbackId(
+ long endpointId, IContextHubEndpointDiscoveryCallback callback) throws RemoteException {
+ super.registerEndpointDiscoveryCallbackId_enforcePermission();
+ checkEndpointDiscoveryPreconditions();
+ mHubInfoRegistry.registerEndpointDiscoveryCallback(endpointId, callback);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void registerEndpointDiscoveryCallbackDescriptor(
+ String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback)
+ throws RemoteException {
+ super.registerEndpointDiscoveryCallbackDescriptor_enforcePermission();
+ checkEndpointDiscoveryPreconditions();
+ mHubInfoRegistry.registerEndpointDiscoveryCallback(serviceDescriptor, callback);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback)
+ throws RemoteException {
+ super.unregisterEndpointDiscoveryCallback_enforcePermission();
+ checkEndpointDiscoveryPreconditions();
+ mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback);
+ }
+
+ private void checkEndpointDiscoveryPreconditions() {
+ if (mHubInfoRegistry == null) {
+ Log.e(TAG, "Hub endpoint registry failed to initialize");
+ throw new UnsupportedOperationException("Endpoint discovery is not supported");
+ }
+ }
+
/**
* Creates an internal load transaction callback to be used for old API clients
*
@@ -936,8 +1070,7 @@ public class ContextHubService extends IContextHubService.Stub {
private void handleClientMessageCallback(int contextHubId, short hostEndpointId,
NanoAppMessage message, List<String> nanoappPermissions,
List<String> messagePermissions) {
- if (!Flags.reliableMessageImplementation()
- || !Flags.reliableMessageDuplicateDetectionService()) {
+ if (!Flags.reliableMessageDuplicateDetectionService()) {
byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId,
message, nanoappPermissions, messagePermissions);
if (message.isReliable() && errorCode != ErrorCode.OK) {
@@ -1031,12 +1164,8 @@ public class ContextHubService extends IContextHubService.Stub {
* @param messageSequenceNumber the message sequence number
* @param errorCode the error code, one of the enum ErrorCode
*/
- private void sendMessageDeliveryStatusToContextHub(int contextHubId,
- int messageSequenceNumber, byte errorCode) {
- if (!Flags.reliableMessageImplementation()) {
- return;
- }
-
+ private void sendMessageDeliveryStatusToContextHub(
+ int contextHubId, int messageSequenceNumber, byte errorCode) {
MessageDeliveryStatus status = new MessageDeliveryStatus();
status.messageSequenceNumber = messageSequenceNumber;
status.errorCode = errorCode;
@@ -1414,6 +1543,8 @@ public class ContextHubService extends IContextHubService.Stub {
}
}
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ pw = ipw;
pw.println("Dumping ContextHub Service");
pw.println("");
@@ -1425,6 +1556,11 @@ public class ContextHubService extends IContextHubService.Stub {
pw.println("Supported permissions: "
+ Arrays.toString(mSupportedContextHubPerms.toArray()));
pw.println("");
+
+ if (mHubInfoRegistry != null) {
+ mHubInfoRegistry.dump(ipw);
+ }
+
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 33d2ff02986a..05be427f9daf 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -18,6 +18,9 @@ package com.android.server.location.contexthub;
import android.Manifest;
import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.V1_0.AsyncEventType;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.HostEndPoint;
@@ -415,4 +418,51 @@ import java.util.List;
static String formatDateFromTimestamp(long timeStampInMs) {
return DATE_FORMATTER.format(Instant.ofEpochMilli(timeStampInMs));
}
+
+ /**
+ * Converts a context hub HAL EndpointInfo object based on the provided HubEndpointInfo.
+ *
+ * @param info the HubEndpointInfo object
+ * @return the equivalent EndpointInfo object
+ */
+ /* package */
+ static EndpointInfo convertHalEndpointInfo(HubEndpointInfo info) {
+ return createHalEndpointInfo(
+ info, info.getIdentifier().getEndpoint(), info.getIdentifier().getHub());
+ }
+
+ /**
+ * Creates a context hub HAL EndpointInfo object based on the provided HubEndpointInfo. As
+ * opposed to convertHalEndpointInfo, this method can be used to overwrite/specify the endpoint
+ * and hub ID.
+ *
+ * @param info the HubEndpointInfo object
+ * @param endpointId the endpoint ID of this object
+ * @param hubId the hub ID of this object
+ * @return the equivalent EndpointInfo object
+ */
+ /* package */
+ static EndpointInfo createHalEndpointInfo(HubEndpointInfo info, long endpointId, long hubId) {
+ EndpointInfo outputInfo = new EndpointInfo();
+ outputInfo.id = new android.hardware.contexthub.EndpointId();
+ outputInfo.id.id = endpointId;
+ outputInfo.id.hubId = hubId;
+ outputInfo.name = info.getName();
+ outputInfo.version = info.getVersion();
+ outputInfo.tag = info.getTag();
+ Collection<String> permissions = info.getRequiredPermissions();
+ outputInfo.requiredPermissions = permissions.toArray(new String[permissions.size()]);
+ Collection<HubServiceInfo> services = info.getServiceInfoCollection();
+ outputInfo.services = new android.hardware.contexthub.Service[services.size()];
+ int i = 0;
+ for (HubServiceInfo service : services) {
+ outputInfo.services[i] = new android.hardware.contexthub.Service();
+ outputInfo.services[i].format = service.getFormat();
+ outputInfo.services[i].serviceDescriptor = service.getServiceDescriptor();
+ outputInfo.services[i].majorVersion = service.getMajorVersion();
+ outputInfo.services[i].minorVersion = service.getMinorVersion();
+ i++;
+ }
+ return outputInfo;
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index 2a0b1afde27b..ccfa61b400b6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -26,6 +26,8 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Collections;
@@ -43,8 +45,8 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Manages transactions at the Context Hub Service.
- * <p>
- * This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ *
+ * <p>This class maintains a queue of transaction requests made to the ContextHubService by clients,
* and executes them through the Context Hub. At any point in time, either the transaction queue is
* empty, or there is a pending transaction that is waiting for an asynchronous response from the
* hub. This class also handles synchronous errors and timeouts of each transaction.
@@ -52,66 +54,80 @@ import java.util.concurrent.atomic.AtomicInteger;
* @hide
*/
/* package */ class ContextHubTransactionManager {
- private static final String TAG = "ContextHubTransactionManager";
+ protected static final String TAG = "ContextHubTransactionManager";
public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1);
public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT =
RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3);
- private static final int MAX_PENDING_REQUESTS = 10000;
+ // TODO: b/362299144: When cleaning up the flag
+ // reduce_locking_context_hub_transaction_manager, change these to private
+ protected static final int MAX_PENDING_REQUESTS = 10000;
- private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
+ protected static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
- private static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250);
+ protected static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250);
- private static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000);
+ protected static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000);
- private final IContextHubWrapper mContextHubProxy;
+ protected final IContextHubWrapper mContextHubProxy;
- private final ContextHubClientManager mClientManager;
+ protected final ContextHubClientManager mClientManager;
- private final NanoAppStateManager mNanoAppStateManager;
+ protected final NanoAppStateManager mNanoAppStateManager;
- private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
+ @GuardedBy("mTransactionLock")
+ protected final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
- private final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap =
+ @GuardedBy("mReliableMessageLock")
+ protected final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap =
new HashMap<>();
/** A set of host endpoint IDs that have an active pending transaction. */
- private final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>();
+ @GuardedBy("mReliableMessageLock")
+ protected final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>();
- private final AtomicInteger mNextAvailableId = new AtomicInteger();
+ protected final AtomicInteger mNextAvailableId = new AtomicInteger();
/**
- * The next available message sequence number. We choose a random
- * number to start with to avoid collisions and limit the bound to
- * half of the max value to avoid overflow.
+ * The next available message sequence number. We choose a random number to start with to avoid
+ * collisions and limit the bound to half of the max value to avoid overflow.
*/
- private final AtomicInteger mNextAvailableMessageSequenceNumber =
+ protected final AtomicInteger mNextAvailableMessageSequenceNumber =
new AtomicInteger(new Random().nextInt(Integer.MAX_VALUE / 2));
/*
- * An executor and the future object for scheduling timeout timers and
+ * An executor and the future objects for scheduling timeout timers and
* for scheduling the processing of reliable message transactions.
*/
- private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
- private ScheduledFuture<?> mTimeoutFuture = null;
- private ScheduledFuture<?> mReliableMessageTransactionFuture = null;
+ protected final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(2);
+
+ @GuardedBy("mTransactionLock")
+ protected ScheduledFuture<?> mTimeoutFuture = null;
+
+ @GuardedBy("mReliableMessageLock")
+ protected ScheduledFuture<?> mReliableMessageTransactionFuture = null;
/*
* The list of previous transaction records.
*/
- private static final int NUM_TRANSACTION_RECORDS = 20;
- private final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque =
+ protected static final int NUM_TRANSACTION_RECORDS = 20;
+ protected final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque =
new ConcurrentLinkedEvictingDeque<>(NUM_TRANSACTION_RECORDS);
- /**
- * A container class to store a record of transactions.
+ /*
+ * Locks for synchronization of normal transactions separately from reliable message
+ * transactions.
*/
- private class TransactionRecord {
- private final String mTransaction;
- private final long mTimestamp;
+ protected final Object mTransactionLock = new Object();
+ protected final Object mReliableMessageLock = new Object();
+ protected final Object mTransactionRecordLock = new Object();
+
+ /** A container class to store a record of transactions. */
+ protected static class TransactionRecord {
+ protected final String mTransaction;
+ protected final long mTimestamp;
TransactionRecord(String transaction) {
mTransaction = transaction;
@@ -126,8 +142,18 @@ import java.util.concurrent.atomic.AtomicInteger;
}
}
+ /** Used when finishing a transaction. */
+ interface TransactionAcceptConditions {
+ /**
+ * Returns whether to accept the found transaction when receiving a response from the
+ * Context Hub.
+ */
+ boolean acceptTransaction(ContextHubServiceTransaction transaction);
+ }
+
/* package */ ContextHubTransactionManager(
- IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager,
+ IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager,
NanoAppStateManager nanoAppStateManager) {
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
@@ -409,34 +435,47 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Adds a new transaction to the queue.
- * <p>
- * If there was no pending transaction at the time, the transaction that was added will be
+ *
+ * <p>If there was no pending transaction at the time, the transaction that was added will be
* started in this method. If there were too many transactions in the queue, an exception will
* be thrown.
*
* @param transaction the transaction to add
- * @throws IllegalStateException if the queue is full
*/
/* package */
- synchronized void addTransaction(
- ContextHubServiceTransaction transaction) throws IllegalStateException {
+ void addTransaction(ContextHubServiceTransaction transaction) {
if (transaction == null) {
return;
}
- if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS
- || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
- throw new IllegalStateException("Transaction queue is full (capacity = "
- + MAX_PENDING_REQUESTS + ")");
+ synchronized (mTransactionRecordLock) {
+ mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
}
- mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
if (Flags.reliableMessageRetrySupportService()
&& transaction.getTransactionType()
== ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
- mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction);
+ synchronized (mReliableMessageLock) {
+ if (mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException(
+ "Reliable message transaction queue is full "
+ + "(capacity = "
+ + MAX_PENDING_REQUESTS
+ + ")");
+ }
+ mReliableMessageTransactionMap.put(
+ transaction.getMessageSequenceNumber(), transaction);
+ }
mExecutor.execute(() -> processMessageTransactions());
- } else {
+ return;
+ }
+
+ synchronized (mTransactionLock) {
+ if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException(
+ "Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")");
+ }
+
mTransactionQueue.add(transaction);
if (mTransactionQueue.size() == 1) {
startNextTransaction();
@@ -448,62 +487,92 @@ import java.util.concurrent.atomic.AtomicInteger;
* Handles a transaction response from a Context Hub.
*
* @param transactionId the transaction ID of the response
- * @param success true if the transaction succeeded
+ * @param success true if the transaction succeeded
*/
/* package */
- synchronized void onTransactionResponse(int transactionId, boolean success) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ void onTransactionResponse(int transactionId, boolean success) {
+ TransactionAcceptConditions conditions =
+ transaction -> {
+ if (transaction.getTransactionId() != transactionId) {
+ Log.w(
+ TAG,
+ "Unexpected transaction: expected "
+ + transactionId
+ + ", received "
+ + transaction.getTransactionId());
+ return false;
+ }
+ return true;
+ };
+ ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
- return;
- }
- if (transaction.getTransactionId() != transactionId) {
- Log.w(TAG, "Received unexpected transaction response (expected ID = "
- + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
+ Log.w(TAG, "Received unexpected transaction response");
return;
}
- transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
- ContextHubTransaction.RESULT_FAILED_AT_HUB);
- removeTransactionAndStartNext();
+ synchronized (transaction) {
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ transaction.setComplete();
+ }
}
+ /**
+ * Handles a message delivery response from a Context Hub.
+ *
+ * @param messageSequenceNumber the message sequence number of the response
+ * @param success true if the message was delivered successfully
+ */
/* package */
- synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
+ void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
if (!Flags.reliableMessageRetrySupportService()) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ TransactionAcceptConditions conditions =
+ transaction -> transaction.getTransactionType()
+ == ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+ && transaction.getMessageSequenceNumber()
+ == messageSequenceNumber;
+ ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ Log.w(TAG, "Received unexpected message delivery response (expected"
+ + " message sequence number = "
+ + messageSequenceNumber
+ + ", received messageSequenceNumber = "
+ + messageSequenceNumber
+ + ")");
return;
}
- int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
- if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
- || transactionMessageSequenceNumber != messageSequenceNumber) {
- Log.w(TAG, "Received unexpected message transaction response (expected message "
- + "sequence number = "
- + transaction.getMessageSequenceNumber()
- + ", received messageSequenceNumber = " + messageSequenceNumber + ")");
- return;
+ synchronized (transaction) {
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ transaction.setComplete();
}
-
- transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
- ContextHubTransaction.RESULT_FAILED_AT_HUB);
- removeTransactionAndStartNext();
return;
}
- ContextHubServiceTransaction transaction =
- mReliableMessageTransactionMap.get(messageSequenceNumber);
- if (transaction == null) {
- Log.w(TAG, "Could not find reliable message transaction with "
- + "message sequence number = "
- + messageSequenceNumber);
- return;
+ ContextHubServiceTransaction transaction = null;
+ synchronized (mReliableMessageLock) {
+ transaction = mReliableMessageTransactionMap.get(messageSequenceNumber);
+ if (transaction == null) {
+ Log.w(
+ TAG,
+ "Could not find reliable message transaction with "
+ + "message sequence number = "
+ + messageSequenceNumber);
+ return;
+ }
+
+ removeMessageTransaction(transaction);
}
- completeMessageTransaction(transaction,
- success ? ContextHubTransaction.RESULT_SUCCESS
+ completeMessageTransaction(
+ transaction,
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
: ContextHubTransaction.RESULT_FAILED_AT_HUB);
mExecutor.execute(() -> processMessageTransactions());
}
@@ -514,77 +583,116 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param nanoAppStateList the list of nanoapps included in the response
*/
/* package */
- synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+ TransactionAcceptConditions conditions = transaction ->
+ transaction.getTransactionType() == ContextHubTransaction.TYPE_QUERY_NANOAPPS;
+ ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected query response (no transaction pending)");
- return;
- }
- if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
- Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+ Log.w(TAG, "Received unexpected query response");
return;
}
- transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
- removeTransactionAndStartNext();
+ synchronized (transaction) {
+ transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
+ transaction.setComplete();
+ }
}
- /**
- * Handles a hub reset event by stopping a pending transaction and starting the next.
- */
+ /** Handles a hub reset event by stopping a pending transaction and starting the next. */
/* package */
- synchronized void onHubReset() {
+ void onHubReset() {
if (Flags.reliableMessageRetrySupportService()) {
- Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
- mReliableMessageTransactionMap.entrySet().iterator();
- while (iter.hasNext()) {
- completeMessageTransaction(iter.next().getValue(),
- ContextHubTransaction.RESULT_FAILED_AT_HUB, iter);
+ synchronized (mReliableMessageLock) {
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ removeAndCompleteMessageTransaction(
+ iter.next().getValue(),
+ ContextHubTransaction.RESULT_FAILED_AT_HUB,
+ iter);
+ }
}
}
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
- if (transaction == null) {
- return;
+ synchronized (mTransactionLock) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ return;
+ }
+
+ removeTransactionAndStartNext();
}
+ }
- removeTransactionAndStartNext();
+ /**
+ * This function also starts the next transaction and removes the active transaction from the
+ * queue. The caller should complete the transaction.
+ *
+ * <p>Returns the active transaction if the transaction queue is not empty, the transaction is
+ * not null, and the transaction matches the conditions.
+ */
+ private ContextHubServiceTransaction getTransactionAndHandleNext(
+ TransactionAcceptConditions conditions) {
+ ContextHubServiceTransaction transaction = null;
+ synchronized (mTransactionLock) {
+ transaction = mTransactionQueue.peek();
+ if (transaction == null
+ || (conditions != null && !conditions.acceptTransaction(transaction))) {
+ return null;
+ }
+
+ cancelTimeoutFuture();
+ mTransactionQueue.remove();
+ if (!mTransactionQueue.isEmpty()) {
+ startNextTransaction();
+ }
+ }
+ return transaction;
}
/**
* Pops the front transaction from the queue and starts the next pending transaction request.
- * <p>
- * Removing elements from the transaction queue must only be done through this method. When a
+ *
+ * <p>Removing elements from the transaction queue must only be done through this method. When a
* pending transaction is removed, the timeout timer is cancelled and the transaction is marked
* complete.
- * <p>
- * It is assumed that the transaction queue is non-empty when this method is invoked, and that
- * the caller has obtained a lock on this ContextHubTransactionManager object.
+ *
+ * <p>It is assumed that the transaction queue is non-empty when this method is invoked, and
+ * that the caller has obtained mTransactionLock.
*/
+ @GuardedBy("mTransactionLock")
private void removeTransactionAndStartNext() {
- if (mTimeoutFuture != null) {
- mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
- mTimeoutFuture = null;
- }
+ cancelTimeoutFuture();
ContextHubServiceTransaction transaction = mTransactionQueue.remove();
- transaction.setComplete();
+ synchronized (transaction) {
+ transaction.setComplete();
+ }
if (!mTransactionQueue.isEmpty()) {
startNextTransaction();
}
}
+ /** Cancels the timeout future. */
+ @GuardedBy("mTransactionLock")
+ private void cancelTimeoutFuture() {
+ if (mTimeoutFuture != null) {
+ mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mTimeoutFuture = null;
+ }
+ }
+
/**
* Starts the next pending transaction request.
- * <p>
- * Starting new transactions must only be done through this method. This method continues to
+ *
+ * <p>Starting new transactions must only be done through this method. This method continues to
* process the transaction queue as long as there are pending requests, and no transaction is
* pending.
- * <p>
- * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
- * object.
+ *
+ * <p>It is assumed that the caller has obtained a lock on mTransactionLock.
*/
+ @GuardedBy("mTransactionLock")
private void startNextTransaction() {
int result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
while (result != ContextHubTransaction.RESULT_SUCCESS && !mTransactionQueue.isEmpty()) {
@@ -592,28 +700,36 @@ import java.util.concurrent.atomic.AtomicInteger;
result = transaction.onTransact();
if (result == ContextHubTransaction.RESULT_SUCCESS) {
- Runnable onTimeoutFunc = () -> {
- synchronized (this) {
- if (!transaction.isComplete()) {
- Log.d(TAG, transaction + " timed out");
- transaction.onTransactionComplete(
- ContextHubTransaction.RESULT_FAILED_TIMEOUT);
-
- removeTransactionAndStartNext();
- }
- }
- };
+ Runnable onTimeoutFunc =
+ () -> {
+ synchronized (transaction) {
+ if (!transaction.isComplete()) {
+ Log.d(TAG, transaction + " timed out");
+ transaction.onTransactionComplete(
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT);
+ transaction.setComplete();
+ }
+ }
+
+ synchronized (mTransactionLock) {
+ removeTransactionAndStartNext();
+ }
+ };
long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
try {
- mTimeoutFuture = mExecutor.schedule(
- onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
+ mTimeoutFuture =
+ mExecutor.schedule(onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Log.e(TAG, "Error when schedule a timer", e);
}
} else {
- transaction.onTransactionComplete(
- ContextHubServiceUtil.toTransactionResult(result));
+ synchronized (transaction) {
+ transaction.onTransactionComplete(
+ ContextHubServiceUtil.toTransactionResult(result));
+ transaction.setComplete();
+ }
+
mTransactionQueue.remove();
}
}
@@ -621,81 +737,97 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Processes message transactions, starting and completing them as needed.
- * <p>
- * This function is called when adding a message transaction or when a timer
- * expires for an existing message transaction's retry or timeout. The
- * internal processing loop will iterate at most twice as if one iteration
- * completes a transaction, the next iteration can only start new transactions.
- * If the first iteration does not complete any transaction, the loop will
- * only iterate once.
+ *
+ * <p>This function is called when adding a message transaction or when a timer expires for an
+ * existing message transaction's retry or timeout. The internal processing loop will iterate at
+ * most twice as if one iteration completes a transaction, the next iteration can only start new
+ * transactions. If the first iteration does not complete any transaction, the loop will only
+ * iterate once.
+ *
* <p>
*/
- private synchronized void processMessageTransactions() {
- if (!Flags.reliableMessageRetrySupportService()) {
- return;
- }
-
- if (mReliableMessageTransactionFuture != null) {
- mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
- mReliableMessageTransactionFuture = null;
- }
+ private void processMessageTransactions() {
+ synchronized (mReliableMessageLock) {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ return;
+ }
- long now = SystemClock.elapsedRealtimeNanos();
- long nextExecutionTime = Long.MAX_VALUE;
- boolean continueProcessing;
- do {
- continueProcessing = false;
- Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
- mReliableMessageTransactionMap.entrySet().iterator();
- while (iter.hasNext()) {
- ContextHubServiceTransaction transaction = iter.next().getValue();
- short hostEndpointId = transaction.getHostEndpointId();
- int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
- if (numCompletedStartCalls == 0
- && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
- continue;
- }
+ if (mReliableMessageTransactionFuture != null) {
+ mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mReliableMessageTransactionFuture = null;
+ }
- long nextRetryTime = transaction.getNextRetryTime();
- long timeoutTime = transaction.getTimeoutTime();
- boolean transactionTimedOut = timeoutTime <= now;
- boolean transactionHitMaxRetries = nextRetryTime <= now
- && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
- if (transactionTimedOut || transactionHitMaxRetries) {
- completeMessageTransaction(transaction,
- ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
- continueProcessing = true;
- } else {
- if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
- startMessageTransaction(transaction, now);
+ long now = SystemClock.elapsedRealtimeNanos();
+ long nextExecutionTime = Long.MAX_VALUE;
+ boolean continueProcessing;
+ do {
+ continueProcessing = false;
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ ContextHubServiceTransaction transaction = iter.next().getValue();
+ short hostEndpointId = transaction.getHostEndpointId();
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ if (numCompletedStartCalls == 0
+ && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
+ continue;
}
- nextExecutionTime = Math.min(nextExecutionTime,
- transaction.getNextRetryTime());
- nextExecutionTime = Math.min(nextExecutionTime,
- transaction.getTimeoutTime());
+ long nextRetryTime = transaction.getNextRetryTime();
+ long timeoutTime = transaction.getTimeoutTime();
+ boolean transactionTimedOut = timeoutTime <= now;
+ boolean transactionHitMaxRetries =
+ nextRetryTime <= now
+ && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
+ if (transactionTimedOut || transactionHitMaxRetries) {
+ removeAndCompleteMessageTransaction(
+ transaction, ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
+ continueProcessing = true;
+ } else {
+ if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
+ startMessageTransaction(transaction, now);
+ }
+
+ nextExecutionTime =
+ Math.min(nextExecutionTime, transaction.getNextRetryTime());
+ nextExecutionTime =
+ Math.min(nextExecutionTime, transaction.getTimeoutTime());
+ }
}
+ } while (continueProcessing);
+
+ if (nextExecutionTime < Long.MAX_VALUE) {
+ mReliableMessageTransactionFuture =
+ mExecutor.schedule(
+ () -> processMessageTransactions(),
+ Math.max(
+ nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
+ RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
+ TimeUnit.NANOSECONDS);
}
- } while (continueProcessing);
-
- if (nextExecutionTime < Long.MAX_VALUE) {
- mReliableMessageTransactionFuture = mExecutor.schedule(
- () -> processMessageTransactions(),
- Math.max(nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
- RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
- TimeUnit.NANOSECONDS);
}
}
/**
- * Completes a message transaction and removes it from the reliable message map.
+ * Completes a message transaction.
*
* @param transaction The transaction to complete.
* @param result The result code.
*/
- private void completeMessageTransaction(ContextHubServiceTransaction transaction,
- @ContextHubTransaction.Result int result) {
- completeMessageTransaction(transaction, result, /* iter= */ null);
+ private void completeMessageTransaction(
+ ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result) {
+ synchronized (transaction) {
+ transaction.onTransactionComplete(result);
+ transaction.setComplete();
+ }
+
+ Log.d(
+ TAG,
+ "Successfully completed reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + " and result = "
+ + result);
}
/**
@@ -705,25 +837,41 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param result The result code.
* @param iter The iterator for the reliable message map - used to remove the message directly.
*/
- private void completeMessageTransaction(ContextHubServiceTransaction transaction,
+ @GuardedBy("mReliableMessageLock")
+ private void removeAndCompleteMessageTransaction(
+ ContextHubServiceTransaction transaction,
@ContextHubTransaction.Result int result,
Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
- transaction.onTransactionComplete(result);
+ removeMessageTransaction(transaction, iter);
+ completeMessageTransaction(transaction, result);
+ }
+ /**
+ * Removes a message transaction from the reliable message map.
+ *
+ * @param transaction The transaction to remove.
+ */
+ @GuardedBy("mReliableMessageLock")
+ private void removeMessageTransaction(ContextHubServiceTransaction transaction) {
+ removeMessageTransaction(transaction, /* iter= */ null);
+ }
+
+ /**
+ * Removes a message transaction from the reliable message map.
+ *
+ * @param transaction The transaction to remove.
+ * @param iter The iterator for the reliable message map - used to remove the message directly.
+ */
+ @GuardedBy("mReliableMessageLock")
+ private void removeMessageTransaction(
+ ContextHubServiceTransaction transaction,
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
if (iter == null) {
mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber());
} else {
iter.remove();
}
mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId());
-
- Log.d(
- TAG,
- "Successfully completed reliable message transaction with "
- + "message sequence number = "
- + transaction.getMessageSequenceNumber()
- + " and result = "
- + result);
}
/**
@@ -732,24 +880,25 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param transaction The transaction to start.
* @param now The now time.
*/
+ @GuardedBy("mReliableMessageLock")
private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) {
int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
@ContextHubTransaction.Result int result = transaction.onTransact();
if (result == ContextHubTransaction.RESULT_SUCCESS) {
- Log.d(
- TAG,
- "Successfully "
- + (numCompletedStartCalls == 0 ? "started" : "retried")
- + " reliable message transaction with message sequence number = "
- + transaction.getMessageSequenceNumber());
+ Log.d(
+ TAG,
+ "Successfully "
+ + (numCompletedStartCalls == 0 ? "started" : "retried")
+ + " reliable message transaction with message sequence number = "
+ + transaction.getMessageSequenceNumber());
} else {
- Log.w(
- TAG,
- "Could not start reliable message transaction with "
- + "message sequence number = "
- + transaction.getMessageSequenceNumber()
- + ", result = "
- + result);
+ Log.w(
+ TAG,
+ "Could not start reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", result = "
+ + result);
}
transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos());
@@ -788,17 +937,19 @@ import java.util.concurrent.atomic.AtomicInteger;
public String toString() {
StringBuilder sb = new StringBuilder();
int i = 0;
- synchronized (this) {
- for (ContextHubServiceTransaction transaction: mTransactionQueue) {
+ synchronized (mTransactionLock) {
+ for (ContextHubServiceTransaction transaction : mTransactionQueue) {
sb.append(i);
sb.append(": ");
sb.append(transaction.toString());
sb.append("\n");
++i;
}
+ }
- if (Flags.reliableMessageRetrySupportService()) {
- for (ContextHubServiceTransaction transaction:
+ if (Flags.reliableMessageRetrySupportService()) {
+ synchronized (mReliableMessageLock) {
+ for (ContextHubServiceTransaction transaction :
mReliableMessageTransactionMap.values()) {
sb.append(i);
sb.append(": ");
@@ -807,7 +958,9 @@ import java.util.concurrent.atomic.AtomicInteger;
++i;
}
}
+ }
+ synchronized (mTransactionRecordLock) {
sb.append("Transaction History:\n");
Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
while (iterator.hasNext()) {
@@ -815,6 +968,7 @@ import java.util.concurrent.atomic.AtomicInteger;
sb.append("\n");
}
}
+
return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java
new file mode 100644
index 000000000000..a67fa308a6ea
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.chre.flags.Flags;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.time.Duration;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages transactions at the Context Hub Service.
+ *
+ * <p>This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ * and executes them through the Context Hub. At any point in time, either the transaction queue is
+ * empty, or there is a pending transaction that is waiting for an asynchronous response from the
+ * hub. This class also handles synchronous errors and timeouts of each transaction.
+ *
+ * <p>This is the old version of ContextHubTransactionManager that uses global synchronization
+ * instead of individual locks. This will be deleted when the
+ * reduce_locking_context_hub_transaction_manager flag is cleaned up.
+ *
+ * @hide
+ */
+/* package */ class ContextHubTransactionManagerOld extends ContextHubTransactionManager {
+ /* package */ ContextHubTransactionManagerOld(
+ IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager,
+ NanoAppStateManager nanoAppStateManager) {
+ super(contextHubProxy, clientManager, nanoAppStateManager);
+ }
+
+ /**
+ * Adds a new transaction to the queue.
+ *
+ * <p>If there was no pending transaction at the time, the transaction that was added will be
+ * started in this method. If there were too many transactions in the queue, an exception will
+ * be thrown.
+ *
+ * @param transaction the transaction to add
+ * @throws IllegalStateException if the queue is full
+ */
+ /* package */
+ @Override
+ synchronized void addTransaction(ContextHubServiceTransaction transaction)
+ throws IllegalStateException {
+ if (transaction == null) {
+ return;
+ }
+
+ if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS
+ || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException(
+ "Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")");
+ }
+
+ mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
+ if (Flags.reliableMessageRetrySupportService()
+ && transaction.getTransactionType()
+ == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
+ mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction);
+ mExecutor.execute(() -> processMessageTransactions());
+ } else {
+ mTransactionQueue.add(transaction);
+ if (mTransactionQueue.size() == 1) {
+ startNextTransaction();
+ }
+ }
+ }
+
+ /**
+ * Handles a transaction response from a Context Hub.
+ *
+ * @param transactionId the transaction ID of the response
+ * @param success true if the transaction succeeded
+ */
+ /* package */
+ @Override
+ synchronized void onTransactionResponse(int transactionId, boolean success) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionId() != transactionId) {
+ Log.w(
+ TAG,
+ "Received unexpected transaction response (expected ID = "
+ + transaction.getTransactionId()
+ + ", received ID = "
+ + transactionId
+ + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ removeTransactionAndStartNext();
+ }
+
+ /* package */
+ @Override
+ synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+
+ int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+ || transactionMessageSequenceNumber != messageSequenceNumber) {
+ Log.w(
+ TAG,
+ "Received unexpected message transaction response (expected message "
+ + "sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", received messageSequenceNumber = "
+ + messageSequenceNumber
+ + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ removeTransactionAndStartNext();
+ return;
+ }
+
+ ContextHubServiceTransaction transaction =
+ mReliableMessageTransactionMap.get(messageSequenceNumber);
+ if (transaction == null) {
+ Log.w(
+ TAG,
+ "Could not find reliable message transaction with "
+ + "message sequence number = "
+ + messageSequenceNumber);
+ return;
+ }
+
+ completeMessageTransaction(
+ transaction,
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ mExecutor.execute(() -> processMessageTransactions());
+ }
+
+ /**
+ * Handles a query response from a Context Hub.
+ *
+ * @param nanoAppStateList the list of nanoapps included in the response
+ */
+ /* package */
+ @Override
+ synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected query response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+ Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+ return;
+ }
+
+ transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
+ removeTransactionAndStartNext();
+ }
+
+ /** Handles a hub reset event by stopping a pending transaction and starting the next. */
+ /* package */
+ @Override
+ synchronized void onHubReset() {
+ if (Flags.reliableMessageRetrySupportService()) {
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ completeMessageTransaction(
+ iter.next().getValue(), ContextHubTransaction.RESULT_FAILED_AT_HUB, iter);
+ }
+ }
+
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ return;
+ }
+
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Pops the front transaction from the queue and starts the next pending transaction request.
+ *
+ * <p>Removing elements from the transaction queue must only be done through this method. When a
+ * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
+ * complete.
+ *
+ * <p>It is assumed that the transaction queue is non-empty when this method is invoked, and
+ * that the caller has obtained a lock on this ContextHubTransactionManager object.
+ */
+ private void removeTransactionAndStartNext() {
+ if (mTimeoutFuture != null) {
+ mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mTimeoutFuture = null;
+ }
+
+ ContextHubServiceTransaction transaction = mTransactionQueue.remove();
+ transaction.setComplete();
+
+ if (!mTransactionQueue.isEmpty()) {
+ startNextTransaction();
+ }
+ }
+
+ /**
+ * Starts the next pending transaction request.
+ *
+ * <p>Starting new transactions must only be done through this method. This method continues to
+ * process the transaction queue as long as there are pending requests, and no transaction is
+ * pending.
+ *
+ * <p>It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
+ * object.
+ */
+ private void startNextTransaction() {
+ int result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ while (result != ContextHubTransaction.RESULT_SUCCESS && !mTransactionQueue.isEmpty()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ result = transaction.onTransact();
+
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
+ Runnable onTimeoutFunc =
+ () -> {
+ synchronized (this) {
+ if (!transaction.isComplete()) {
+ Log.d(TAG, transaction + " timed out");
+ transaction.onTransactionComplete(
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT);
+
+ removeTransactionAndStartNext();
+ }
+ }
+ };
+
+ long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
+ try {
+ mTimeoutFuture =
+ mExecutor.schedule(onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, "Error when schedule a timer", e);
+ }
+ } else {
+ transaction.onTransactionComplete(
+ ContextHubServiceUtil.toTransactionResult(result));
+ mTransactionQueue.remove();
+ }
+ }
+ }
+
+ /**
+ * Processes message transactions, starting and completing them as needed.
+ *
+ * <p>This function is called when adding a message transaction or when a timer expires for an
+ * existing message transaction's retry or timeout. The internal processing loop will iterate at
+ * most twice as if one iteration completes a transaction, the next iteration can only start new
+ * transactions. If the first iteration does not complete any transaction, the loop will only
+ * iterate once.
+ *
+ * <p>
+ */
+ private synchronized void processMessageTransactions() {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ return;
+ }
+
+ if (mReliableMessageTransactionFuture != null) {
+ mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mReliableMessageTransactionFuture = null;
+ }
+
+ long now = SystemClock.elapsedRealtimeNanos();
+ long nextExecutionTime = Long.MAX_VALUE;
+ boolean continueProcessing;
+ do {
+ continueProcessing = false;
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ ContextHubServiceTransaction transaction = iter.next().getValue();
+ short hostEndpointId = transaction.getHostEndpointId();
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ if (numCompletedStartCalls == 0
+ && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
+ continue;
+ }
+
+ long nextRetryTime = transaction.getNextRetryTime();
+ long timeoutTime = transaction.getTimeoutTime();
+ boolean transactionTimedOut = timeoutTime <= now;
+ boolean transactionHitMaxRetries =
+ nextRetryTime <= now
+ && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
+ if (transactionTimedOut || transactionHitMaxRetries) {
+ completeMessageTransaction(
+ transaction, ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
+ continueProcessing = true;
+ } else {
+ if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
+ startMessageTransaction(transaction, now);
+ }
+
+ nextExecutionTime = Math.min(nextExecutionTime, transaction.getNextRetryTime());
+ nextExecutionTime = Math.min(nextExecutionTime, transaction.getTimeoutTime());
+ }
+ }
+ } while (continueProcessing);
+
+ if (nextExecutionTime < Long.MAX_VALUE) {
+ mReliableMessageTransactionFuture =
+ mExecutor.schedule(
+ () -> processMessageTransactions(),
+ Math.max(
+ nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
+ RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
+ TimeUnit.NANOSECONDS);
+ }
+ }
+
+ /**
+ * Completes a message transaction and removes it from the reliable message map.
+ *
+ * @param transaction The transaction to complete.
+ * @param result The result code.
+ */
+ private void completeMessageTransaction(
+ ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result) {
+ completeMessageTransaction(transaction, result, /* iter= */ null);
+ }
+
+ /**
+ * Completes a message transaction and removes it from the reliable message map using iter.
+ *
+ * @param transaction The transaction to complete.
+ * @param result The result code.
+ * @param iter The iterator for the reliable message map - used to remove the message directly.
+ */
+ private void completeMessageTransaction(
+ ContextHubServiceTransaction transaction,
+ @ContextHubTransaction.Result int result,
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
+ transaction.onTransactionComplete(result);
+
+ if (iter == null) {
+ mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber());
+ } else {
+ iter.remove();
+ }
+ mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId());
+
+ Log.d(
+ TAG,
+ "Successfully completed reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + " and result = "
+ + result);
+ }
+
+ /**
+ * Starts a message transaction.
+ *
+ * @param transaction The transaction to start.
+ * @param now The now time.
+ */
+ private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) {
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ @ContextHubTransaction.Result int result = transaction.onTransact();
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
+ Log.d(
+ TAG,
+ "Successfully "
+ + (numCompletedStartCalls == 0 ? "started" : "retried")
+ + " reliable message transaction with message sequence number = "
+ + transaction.getMessageSequenceNumber());
+ } else {
+ Log.w(
+ TAG,
+ "Could not start reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", result = "
+ + result);
+ }
+
+ transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos());
+ if (transaction.getTimeoutTime() == Long.MAX_VALUE) { // first time starting transaction
+ transaction.setTimeoutTime(now + RELIABLE_MESSAGE_TIMEOUT.toNanos());
+ }
+ transaction.setNumCompletedStartCalls(numCompletedStartCalls + 1);
+ mReliableMessageHostEndpointIdActiveSet.add(transaction.getHostEndpointId());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ synchronized (this) {
+ for (ContextHubServiceTransaction transaction : mTransactionQueue) {
+ sb.append(i);
+ sb.append(": ");
+ sb.append(transaction.toString());
+ sb.append("\n");
+ ++i;
+ }
+
+ if (Flags.reliableMessageRetrySupportService()) {
+ for (ContextHubServiceTransaction transaction :
+ mReliableMessageTransactionMap.values()) {
+ sb.append(i);
+ sb.append(": ");
+ sb.append(transaction.toString());
+ sb.append("\n");
+ ++i;
+ }
+ }
+
+ sb.append("Transaction History:\n");
+ Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
+ while (iterator.hasNext()) {
+ sb.append(iterator.next());
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
new file mode 100644
index 000000000000..6f5f191849e2
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubServiceInfo;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
+import android.hardware.location.HubInfo;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+
+class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
+ private static final String TAG = "HubInfoRegistry";
+ private final Object mLock = new Object();
+
+ private final IContextHubWrapper mContextHubWrapper;
+
+ @GuardedBy("mLock")
+ private List<HubInfo> mHubsInfo;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<HubEndpointInfo.HubEndpointIdentifier, HubEndpointInfo>
+ mHubEndpointInfos = new ArrayMap<>();
+
+ /**
+ * A wrapper class that is used to store arguments to
+ * ContextHubManager.registerEndpointCallback.
+ */
+ private static class DiscoveryCallback {
+ private final IContextHubEndpointDiscoveryCallback mCallback;
+ private final Optional<Long> mEndpointId;
+ private final Optional<String> mServiceDescriptor;
+
+ DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) {
+ mCallback = callback;
+ mEndpointId = Optional.of(endpointId);
+ mServiceDescriptor = Optional.empty();
+ }
+
+ DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) {
+ mCallback = callback;
+ mEndpointId = Optional.empty();
+ mServiceDescriptor = Optional.of(serviceDescriptor);
+ }
+
+ public IContextHubEndpointDiscoveryCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * @param info The hub endpoint info to check
+ * @return true if info matches
+ */
+ public boolean isMatch(HubEndpointInfo info) {
+ if (mEndpointId.isPresent()) {
+ return mEndpointId.get() == info.getIdentifier().getEndpoint();
+ }
+ if (mServiceDescriptor.isPresent()) {
+ for (HubServiceInfo serviceInfo : info.getServiceInfoCollection()) {
+ if (mServiceDescriptor.get().equals(serviceInfo.getServiceDescriptor())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /* The list of discovery callbacks registered with the service */
+ @GuardedBy("mCallbackLock")
+ private final List<DiscoveryCallback> mEndpointDiscoveryCallbacks = new ArrayList<>();
+
+ private final Object mCallbackLock = new Object();
+
+ HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
+ mContextHubWrapper = contextHubWrapper;
+ refreshCachedHubs();
+ refreshCachedEndpoints();
+ }
+
+ /** Retrieve the list of hubs available. */
+ List<HubInfo> getHubs() {
+ synchronized (mLock) {
+ return mHubsInfo;
+ }
+ }
+
+ private void refreshCachedHubs() {
+ List<HubInfo> hubInfos;
+ try {
+ hubInfos = mContextHubWrapper.getHubs();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Hub info", e);
+ hubInfos = Collections.emptyList();
+ }
+
+ synchronized (mLock) {
+ mHubsInfo = hubInfos;
+ }
+ }
+
+ private void refreshCachedEndpoints() {
+ List<HubEndpointInfo> endpointInfos;
+ try {
+ endpointInfos = mContextHubWrapper.getEndpoints();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Hub info", e);
+ endpointInfos = Collections.emptyList();
+ }
+
+ synchronized (mLock) {
+ mHubEndpointInfos.clear();
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+ }
+ }
+ }
+
+ public HubEndpointInfo getEndpointInfo(HubEndpointInfo.HubEndpointIdentifier id) {
+ synchronized (mLock) {
+ return mHubEndpointInfos.get(id);
+ }
+ }
+
+ /** Invoked when HAL restarts */
+ public void onHalRestart() {
+ synchronized (mLock) {
+ refreshCachedHubs();
+ refreshCachedEndpoints();
+ }
+ }
+
+ @Override
+ public void onEndpointStarted(HubEndpointInfo[] endpointInfos) {
+ synchronized (mLock) {
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ mHubEndpointInfos.remove(endpointInfo.getIdentifier());
+ mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+ }
+ }
+
+ invokeForMatchingEndpoints(
+ endpointInfos,
+ (cb, infoList) -> {
+ try {
+ cb.onEndpointsStarted(infoList);
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ Log.w(TAG, "onEndpointStarted: callback died, unregistering");
+ unregisterEndpointDiscoveryCallback(cb);
+ } else {
+ Log.e(TAG, "Exception while calling onEndpointsStarted", e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onEndpointStopped(
+ HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason) {
+ ArrayList<HubEndpointInfo> removedInfoList = new ArrayList<>();
+ synchronized (mLock) {
+ for (HubEndpointInfo.HubEndpointIdentifier endpointId : endpointIds) {
+ HubEndpointInfo info = mHubEndpointInfos.remove(endpointId);
+ if (info != null) {
+ removedInfoList.add(info);
+ }
+ }
+ }
+
+ invokeForMatchingEndpoints(
+ removedInfoList.toArray(new HubEndpointInfo[removedInfoList.size()]),
+ (cb, infoList) -> {
+ try {
+ cb.onEndpointsStopped(infoList, reason);
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ Log.w(TAG, "onEndpointStopped: callback died, unregistering");
+ unregisterEndpointDiscoveryCallback(cb);
+ } else {
+ Log.e(TAG, "Exception while calling onEndpointsStopped", e);
+ }
+ }
+ });
+ }
+
+ /** Return a list of {@link HubEndpointInfo} that represents endpoints with the matching id. */
+ public List<HubEndpointInfo> findEndpoints(long endpointIdQuery) {
+ List<HubEndpointInfo> searchResult = new ArrayList<>();
+ synchronized (mLock) {
+ for (HubEndpointInfo.HubEndpointIdentifier endpointId : mHubEndpointInfos.keySet()) {
+ if (endpointId.getEndpoint() == endpointIdQuery) {
+ searchResult.add(mHubEndpointInfos.get(endpointId));
+ }
+ }
+ }
+ return searchResult;
+ }
+
+ /**
+ * Return a list of {@link HubEndpointInfo} that represents endpoints with the matching service.
+ */
+ public List<HubEndpointInfo> findEndpointsWithService(String serviceDescriptor) {
+ List<HubEndpointInfo> searchResult = new ArrayList<>();
+ synchronized (mLock) {
+ for (HubEndpointInfo endpointInfo : mHubEndpointInfos.values()) {
+ for (HubServiceInfo serviceInfo : endpointInfo.getServiceInfoCollection()) {
+ if (serviceDescriptor.equals(serviceInfo.getServiceDescriptor())) {
+ searchResult.add(endpointInfo);
+ }
+ }
+ }
+ }
+ return searchResult;
+ }
+
+ /* package */
+ void registerEndpointDiscoveryCallback(
+ long endpointId, IContextHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ synchronized (mCallbackLock) {
+ checkCallbackAlreadyRegistered(callback);
+ mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId));
+ }
+ }
+
+ /* package */
+ void registerEndpointDiscoveryCallback(
+ String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ synchronized (mCallbackLock) {
+ checkCallbackAlreadyRegistered(callback);
+ mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor));
+ }
+ }
+
+ /* package */
+ void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ synchronized (mCallbackLock) {
+ for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+ if (discoveryCallback.getCallback().asBinder() == callback.asBinder()) {
+ mEndpointDiscoveryCallbacks.remove(discoveryCallback);
+ break;
+ }
+ }
+ }
+ }
+
+ private void checkCallbackAlreadyRegistered(
+ IContextHubEndpointDiscoveryCallback callback) {
+ synchronized (mCallbackLock) {
+ for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+ if (discoveryCallback.mCallback.asBinder() == callback.asBinder()) {
+ throw new IllegalArgumentException("Callback is already registered");
+ }
+ }
+ }
+ }
+
+ /**
+ * Iterates through all registered discovery callbacks and invokes a given callback for those
+ * that match the endpoints the callback is targeted for.
+ *
+ * @param endpointInfos The list of endpoint infos to check for a match.
+ * @param consumer The callback to invoke, which consumes the callback object and the list of
+ * matched endpoint infos.
+ */
+ private void invokeForMatchingEndpoints(
+ HubEndpointInfo[] endpointInfos,
+ BiConsumer<IContextHubEndpointDiscoveryCallback, HubEndpointInfo[]> consumer) {
+ synchronized (mCallbackLock) {
+ for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+ ArrayList<HubEndpointInfo> infoList = new ArrayList<>();
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ if (discoveryCallback.isMatch(endpointInfo)) {
+ infoList.add(endpointInfo);
+ }
+ }
+
+ consumer.accept(
+ discoveryCallback.getCallback(),
+ infoList.toArray(new HubEndpointInfo[infoList.size()]));
+ }
+ }
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ synchronized (mLock) {
+ dumpLocked(ipw);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void dumpLocked(IndentingPrintWriter ipw) {
+ ipw.println(TAG);
+
+ ipw.increaseIndent();
+ ipw.println("Hubs");
+ for (HubInfo hubInfo : mHubsInfo) {
+ ipw.println(hubInfo);
+ }
+ ipw.decreaseIndent();
+
+ ipw.println();
+
+ ipw.increaseIndent();
+ ipw.println("Endpoints");
+ for (HubEndpointInfo endpointInfo : mHubEndpointInfos.values()) {
+ ipw.println(endpointInfo);
+ }
+ ipw.decreaseIndent();
+
+ ipw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index a8ad41853d34..6cb942980403 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -18,7 +18,9 @@ package com.android.server.location.contexthub;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.chre.flags.Flags;
+import android.hardware.contexthub.EndpointId;
import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.NanSessionRequest;
import android.hardware.contexthub.V1_0.ContextHub;
@@ -30,9 +32,11 @@ import android.hardware.contexthub.V1_2.HubAppInfo;
import android.hardware.contexthub.V1_2.IContexthubCallback;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
+import android.hardware.location.VendorHubInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -52,13 +56,14 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @hide
*/
public abstract class IContextHubWrapper {
+ private static final boolean DEBUG = false;
private static final String TAG = "IContextHubWrapper";
/**
@@ -217,10 +222,47 @@ public abstract class IContextHubWrapper {
return proxy == null ? null : new ContextHubWrapperAidl(proxy);
}
- /**
- * Calls the appropriate getHubs function depending on the HAL version.
- */
- public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException;
+ /** Calls the appropriate getHubs function depending on the HAL version. */
+ public abstract Pair<List<ContextHubInfo>, List<String>> getContextHubs()
+ throws RemoteException;
+
+ /** Calls the appropriate getHubs function depending on the HAL version. */
+ public List<HubInfo> getHubs() throws RemoteException {
+ return Collections.emptyList();
+ }
+
+ /** Calls the appropriate getEndpoints function depending on the HAL version. */
+ public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+ return Collections.emptyList();
+ }
+
+ /** Calls the appropriate registerEndpointCallback function depending on the HAL version. */
+ public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+ throws RemoteException {}
+
+ /** Registers the endpoint with the ContextHub HAL */
+ public void registerEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {}
+
+ /** Unregisters a previously registered endpoint */
+ public int[] requestSessionIdRange(int size) throws RemoteException {
+ return null;
+ }
+
+ /** Opens an endpoint session between two endpoints */
+ public void openEndpointSession(
+ int sessionId, EndpointId destination, EndpointId initiator, String serviceDescriptor)
+ throws RemoteException {}
+
+ /** Closes a previously opened endpoint */
+ public void closeEndpointSession(int sessionId, byte reason) throws RemoteException {}
+
+ /** Unregisters a previously registered endpoint */
+ public void unregisterEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {}
+
+ /** Notifies the completion of a session opened by the HAL */
+ public void endpointSessionOpenComplete(int sessionId) throws RemoteException {}
/**
* @return True if this version of the Contexthub HAL supports Location setting notifications.
@@ -494,16 +536,11 @@ public abstract class IContextHubWrapper {
}
public void handleMessageDeliveryStatus(
- char hostEndpointId,
- MessageDeliveryStatus messageDeliveryStatus) {
- if (Flags.reliableMessageImplementation()) {
- mHandler.post(() -> {
- mCallback.handleMessageDeliveryStatus(messageDeliveryStatus);
- });
- } else {
- Log.w(TAG, "handleMessageDeliveryStatus called when the "
- + "reliableMessageImplementation flag is disabled");
- }
+ char hostEndpointId, MessageDeliveryStatus messageDeliveryStatus) {
+ mHandler.post(
+ () -> {
+ mCallback.handleMessageDeliveryStatus(messageDeliveryStatus);
+ });
}
public byte[] getUuid() {
@@ -561,7 +598,7 @@ public abstract class IContextHubWrapper {
mIsTestModeEnabled.set(false);
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
@@ -579,6 +616,147 @@ public abstract class IContextHubWrapper {
return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions));
}
+ public List<HubInfo> getHubs() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return Collections.emptyList();
+ }
+
+ List<HubInfo> retVal = new ArrayList<>();
+ final List<android.hardware.contexthub.HubInfo> halHubs = hub.getHubs();
+
+ for (android.hardware.contexthub.HubInfo halHub : halHubs) {
+ /* HAL -> API Type conversion */
+ final HubInfo hubInfo;
+ switch (halHub.hubDetails.getTag()) {
+ case android.hardware.contexthub.HubInfo.HubDetails.contextHubInfo:
+ ContextHubInfo contextHubInfo =
+ new ContextHubInfo(halHub.hubDetails.getContextHubInfo());
+ hubInfo = new HubInfo(halHub.hubId, contextHubInfo);
+ break;
+ case android.hardware.contexthub.HubInfo.HubDetails.vendorHubInfo:
+ VendorHubInfo vendorHubInfo =
+ new VendorHubInfo(halHub.hubDetails.getVendorHubInfo());
+ hubInfo = new HubInfo(halHub.hubId, vendorHubInfo);
+ break;
+ default:
+ Log.w(TAG, "getHubs: invalid hub: " + halHub);
+ // Invalid
+ continue;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getHubs: hubInfo=" + hubInfo);
+ }
+ retVal.add(hubInfo);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getHubs: total count=" + retVal.size());
+ }
+ return retVal;
+ }
+
+ @Override
+ public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return Collections.emptyList();
+ }
+
+ List<HubEndpointInfo> retVal = new ArrayList<>();
+ final List<android.hardware.contexthub.EndpointInfo> halEndpointInfos =
+ hub.getEndpoints();
+ for (android.hardware.contexthub.EndpointInfo halEndpointInfo : halEndpointInfos) {
+ /* HAL -> API Type conversion */
+ final HubEndpointInfo endpointInfo = new HubEndpointInfo(halEndpointInfo);
+ if (DEBUG) {
+ Log.i(TAG, "getEndpoints: endpointInfo=" + endpointInfo);
+ }
+ retVal.add(endpointInfo);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getEndpoints: total count=" + retVal.size());
+ }
+ return retVal;
+ }
+
+ @Override
+ public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "registerEndpointCallback: cb=" + cb);
+ }
+ hub.registerEndpointCallback(cb);
+ }
+
+ @Override
+ public void registerEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.registerEndpoint(info);
+ }
+
+ @Override
+ public int[] requestSessionIdRange(int size) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return null;
+ }
+ return hub.requestSessionIdRange(size);
+ }
+
+ @Override
+ public void openEndpointSession(
+ int sessionId,
+ EndpointId destination,
+ EndpointId initiator,
+ String serviceDescriptor)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.openEndpointSession(sessionId, destination, initiator, serviceDescriptor);
+ }
+
+ @Override
+ public void closeEndpointSession(int sessionId, byte reason) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.closeEndpointSession(sessionId, reason);
+ }
+
+ @Override
+ public void unregisterEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.unregisterEndpoint(info);
+ }
+
+ @Override
+ public void endpointSessionOpenComplete(int sessionId) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.endpointSessionOpenComplete(sessionId);
+ }
+
public boolean supportsLocationSettingNotifications() {
return true;
}
@@ -682,9 +860,8 @@ public abstract class IContextHubWrapper {
// Only process the message normally if not using test mode manager or if
// the test mode manager call returned false as this indicates it did not
// process the message.
- boolean useTestModeManager = Flags.reliableMessageImplementation()
- && Flags.reliableMessageTestModeBehavior()
- && mIsTestModeEnabled.get();
+ boolean useTestModeManager =
+ Flags.reliableMessageTestModeBehavior() && mIsTestModeEnabled.get();
if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(
sendMessage, message)) {
try {
@@ -1067,7 +1244,7 @@ public abstract class IContextHubWrapper {
mHub = hub;
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
for (ContextHub hub : mHub.getHubs()) {
hubInfoList.add(new ContextHubInfo(hub));
@@ -1112,7 +1289,7 @@ public abstract class IContextHubWrapper {
mHub = hub;
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
for (ContextHub hub : mHub.getHubs()) {
hubInfoList.add(new ContextHubInfo(hub));
@@ -1176,7 +1353,7 @@ public abstract class IContextHubWrapper {
mHubInfo = new Pair(hubInfoList, supportedPermissions);
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
mHub.getHubs_1_2(this);
return mHubInfo;
}
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 27ad555f3701..44eb9063b44e 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -16,13 +16,16 @@
package com.android.server.location.fudger;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.location.geometry.S2CellIdUtils;
import java.security.SecureRandom;
import java.time.Clock;
@@ -83,6 +86,9 @@ public class LocationFudger {
@GuardedBy("this")
@Nullable private LocationResult mCachedCoarseLocationResult;
+ @GuardedBy("this")
+ @Nullable private LocationFudgerCache mLocationFudgerCache = null;
+
public LocationFudger(float accuracyM) {
this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom());
}
@@ -97,6 +103,16 @@ public class LocationFudger {
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ synchronized (this) {
+ mLocationFudgerCache = cache;
+ }
+ }
+
+ /**
* Resets the random offsets completely.
*/
public void resetOffsets() {
@@ -162,16 +178,34 @@ public class LocationFudger {
longitude += wrapLongitude(metersToDegreesLongitude(mLongitudeOffsetM, latitude));
latitude += wrapLatitude(metersToDegreesLatitude(mLatitudeOffsetM));
- // quantize location by snapping to a grid. this is the primary means of obfuscation. it
- // gives nice consistent results and is very effective at hiding the true location (as long
- // as you are not sitting on a grid boundary, which the random offsets mitigate).
- //
- // note that we quantize the latitude first, since the longitude quantization depends on the
- // latitude value and so leaks information about the latitude
- double latGranularity = metersToDegreesLatitude(mAccuracyM);
- latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
- double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
- longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ // We copy a reference to the cache, so even if mLocationFudgerCache is concurrently set
+ // to null, we can continue executing the condition below.
+ LocationFudgerCache cacheCopy = null;
+ synchronized (this) {
+ cacheCopy = mLocationFudgerCache;
+ }
+
+ // TODO(b/381204398): To ensure a safe rollout, two algorithms co-exist. The first is the
+ // new density-based algorithm, while the second is the traditional coarsening algorithm.
+ // Once rollout is done, clean up the unused algorithm.
+ if (Flags.densityBasedCoarseLocations() && cacheCopy != null
+ && cacheCopy.hasDefaultValue()) {
+ int level = cacheCopy.getCoarseningLevel(latitude, longitude);
+ double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
+ latitude = center[S2CellIdUtils.LAT_INDEX];
+ longitude = center[S2CellIdUtils.LNG_INDEX];
+ } else {
+ // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+ // gives nice consistent results and is very effective at hiding the true location (as
+ // long as you are not sitting on a grid boundary, which the random offsets mitigate).
+ //
+ // note that we quantize the latitude first, since the longitude quantization depends on
+ // the latitude value and so leaks information about the latitude
+ double latGranularity = metersToDegreesLatitude(mAccuracyM);
+ latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+ double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+ longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ }
coarse.setLatitude(latitude);
coarse.setLongitude(longitude);
@@ -185,6 +219,15 @@ public class LocationFudger {
return coarse;
}
+ @VisibleForTesting
+ protected double[] snapToCenterOfS2Cell(double latDegrees, double lngDegrees, int level) {
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latDegrees, lngDegrees);
+ long coarsenedCell = S2CellIdUtils.getParent(leafCell, level);
+ double[] center = new double[] {0.0, 0.0};
+ S2CellIdUtils.toLatLngDegrees(coarsenedCell, center);
+ return center;
+ }
+
/**
* Update the random offsets over time.
*
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
new file mode 100644
index 000000000000..ce8bec8f0147
--- /dev/null
+++ b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.fudger;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import java.util.Objects;
+
+/**
+ * A cache for returning the coarsening level to be used. The coarsening level depends on the user
+ * location. If the cache contains the requested latitude/longitude, the s2 level of the cached
+ * cell id is returned. If not, a default value is returned.
+ * This class has a {@link ProxyPopulationDensityProvider} used to refresh the cache.
+ * This cache exists because {@link ProxyPopulationDensityProvider} must be queried asynchronously,
+ * whereas a synchronous answer is needed.
+ * The cache is first-in, first-out, and has a fixed size. Cache entries are valid until evicted by
+ * another value.
+ */
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public class LocationFudgerCache {
+
+ // The maximum number of S2 cell ids stored in the cache.
+ // Each cell id is a long, so the memory requirement is 8*MAX_CACHE_SIZE bytes.
+ protected static final int MAX_CACHE_SIZE = 20;
+
+ private final Object mLock = new Object();
+
+ // mCache is a circular buffer of size MAX_CACHE_SIZE. The next position to be written to is
+ // mPosInCache. Initially, the cache is filled with INVALID_CELL_IDs.
+ @GuardedBy("mLock")
+ private final long[] mCache = new long[MAX_CACHE_SIZE];
+
+ @GuardedBy("mLock")
+ private int mPosInCache = 0;
+
+ @GuardedBy("mLock")
+ private int mCacheSize = 0;
+
+ // The S2 level to coarsen to, if the cache doesn't contain a better answer.
+ // Updated concurrently by callbacks.
+ @GuardedBy("mLock")
+ private Integer mDefaultCoarseningLevel = null;
+
+ // The provider that asynchronously provides what is stored in the cache.
+ private final ProxyPopulationDensityProvider mPopulationDensityProvider;
+
+ private static String sTAG = "LocationFudgerCache";
+
+ public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) {
+ mPopulationDensityProvider = Objects.requireNonNull(provider);
+
+ asyncFetchDefaultCoarseningLevel();
+ }
+
+ /** Returns true if the cache has successfully received a default value from the provider. */
+ public boolean hasDefaultValue() {
+ synchronized (mLock) {
+ return (mDefaultCoarseningLevel != null);
+ }
+ }
+
+ /**
+ * Returns the S2 level to which the provided location should be coarsened.
+ * The answer comes from the cache if available, otherwise the default value is returned.
+ */
+ public int getCoarseningLevel(double latitudeDegrees, double longitudeDegrees) {
+ // If we still haven't received the default level from the provider, try fetching it again.
+ // The answer wouldn't come in time, but it will be used for the following queries.
+ if (!hasDefaultValue()) {
+ asyncFetchDefaultCoarseningLevel();
+ }
+ Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees);
+ if (s2CellId == null) {
+ // Asynchronously queries the density from the provider. The answer won't come in time,
+ // but it will update the cache for the following queries.
+ refreshCache(latitudeDegrees, longitudeDegrees);
+
+ return getDefaultCoarseningLevel();
+ }
+ return S2CellIdUtils.getLevel(s2CellId);
+ }
+
+ /**
+ * If the cache contains the current location, returns the corresponding S2 cell id.
+ * Otherwise, returns null.
+ */
+ @Nullable
+ private Long readCacheForLatLng(double latDegrees, double lngDegrees) {
+ synchronized (mLock) {
+ for (int i = 0; i < mCacheSize; i++) {
+ if (S2CellIdUtils.containsLatLngDegrees(mCache[i], latDegrees, lngDegrees)) {
+ return mCache[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Adds the provided s2 cell id to the cache. This might evict other values from the cache. */
+ public void addToCache(long s2CellId) {
+ addToCache(new long[] {s2CellId});
+ }
+
+ /**
+ * Adds the provided s2 cell ids to the cache. This might evict other values from the cache.
+ * If more than MAX_CACHE_SIZE elements are provided, only the first elements are copied.
+ * The first element of the input is added last into the FIFO cache, so it gets evicted last.
+ */
+ public void addToCache(long[] s2CellIds) {
+ synchronized (mLock) {
+ // Only copy up to MAX_CACHE_SIZE elements
+ int end = Math.min(s2CellIds.length, MAX_CACHE_SIZE);
+ mCacheSize = Math.min(mCacheSize + end, MAX_CACHE_SIZE);
+
+ // Add in reverse so the first cell of s2CellIds is the last evicted
+ for (int i = end - 1; i >= 0; i--) {
+ mCache[mPosInCache] = s2CellIds[i];
+ mPosInCache = (mPosInCache + 1) % MAX_CACHE_SIZE;
+ }
+ }
+ }
+
+ /**
+ * Queries the population density provider for the default coarsening level (to be used if the
+ * cache doesn't contain a better answer), and updates mDefaultCoarseningLevel with the answer.
+ */
+ private void asyncFetchDefaultCoarseningLevel() {
+ IS2LevelCallback callback = new IS2LevelCallback.Stub() {
+ @Override
+ public void onResult(int s2level) {
+ synchronized (mLock) {
+ mDefaultCoarseningLevel = Integer.valueOf(s2level);
+ }
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get default population density");
+ }
+ };
+ mPopulationDensityProvider.getDefaultCoarseningLevel(callback);
+ }
+
+ /**
+ * Queries the population density provider and store the result in the cache.
+ */
+ private void refreshCache(double latitude, double longitude) {
+ IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() {
+ @Override
+ public void onResult(long[] s2CellIds) {
+ addToCache(s2CellIds);
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get population density");
+ }
+ };
+ mPopulationDensityProvider.getCoarsenedS2Cells(latitude, longitude, MAX_CACHE_SIZE - 1,
+ callback);
+ }
+
+ /**
+ * Returns the default S2 level to coarsen to. This should be used if the cache
+ * does not provide a better answer.
+ */
+ private int getDefaultCoarseningLevel() {
+ synchronized (mLock) {
+ // The minimum valid level is 0.
+ if (mDefaultCoarseningLevel == null) {
+ return 0;
+ }
+ return mDefaultCoarseningLevel;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4a9bf88aae33..a8c90100ebf0 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -48,6 +48,7 @@ import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -105,6 +106,7 @@ import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.fudger.LocationFudger;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
@@ -1663,6 +1665,18 @@ public class LocationProviderManager extends
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudger.setLocationFudgerCache(cache);
+ }
+
+ /**
* Returns true if this provider is visible to the current caller (whether called from a binder
* thread or not). If a provider isn't visible, then all APIs return the same data they would if
* the provider didn't exist (i.e. the caller can't see or use the provider).
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
new file mode 100644
index 000000000000..7b454e481dda
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider.proxy;
+
+import static android.location.provider.PopulationDensityProviderBase.ACTION_POPULATION_DENSITY_PROVIDER;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.IPopulationDensityProvider;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/**
+ * Proxy for IPopulationDensityProvider implementations.
+ */
+public class ProxyPopulationDensityProvider {
+
+ public static final String TAG = "ProxyPopulationDensityProvider";
+
+ final ServiceWatcher mServiceWatcher;
+
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ public static ProxyPopulationDensityProvider createAndRegister(Context context) {
+ ProxyPopulationDensityProvider proxy = new ProxyPopulationDensityProvider(context);
+ if (proxy.register()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private ProxyPopulationDensityProvider(Context context) {
+ mServiceWatcher = ServiceWatcher.create(
+ context,
+ "PopulationDensityProxy",
+ CurrentUserServiceSupplier.createFromConfig(
+ context,
+ ACTION_POPULATION_DENSITY_PROVIDER,
+ com.android.internal.R.bool.config_enablePopulationDensityProviderOverlay,
+ com.android.internal.R.string.config_populationDensityProviderPackageName),
+ null);
+ }
+
+ private boolean register() {
+ boolean resolves = mServiceWatcher.checkServiceResolves();
+ if (resolves) {
+ mServiceWatcher.register();
+ }
+ return resolves;
+ }
+
+ /** Gets the default coarsening level. */
+ public void getDefaultCoarseningLevel(IS2LevelCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getDefaultCoarseningLevel(callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying default coarsening level");
+ }
+ }
+ });
+ }
+
+
+ /** Gets the population density at the requested location. */
+ public void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+ int numAdditionalCells, IS2CellIdsCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getCoarsenedS2Cells(latitudeDegrees, longitudeDegrees,
+ numAdditionalCells, callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying coarsened S2 cell");
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index c79f41d32b29..2686f2b30d27 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -32,6 +32,7 @@ import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
+import android.media.audio.Flags;
import android.media.audiopolicy.AudioProductStrategy;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -45,6 +46,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -377,7 +379,12 @@ import java.util.Objects;
Slog.e(
TAG,
"Could not map this selected device attribute type to an available route: "
- + selectedDeviceAttributesType);
+ + selectedDeviceAttributesType
+ + ". Available types: "
+ + Arrays.toString(
+ Arrays.stream(audioDeviceInfos)
+ .map(AudioDeviceInfo::getType)
+ .toArray()));
// We know mRouteIdToAvailableDeviceRoutes is not empty.
newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
}
@@ -420,7 +427,7 @@ import java.util.Objects;
// to derive a name ourselves from the type instead.
String deviceName = audioDeviceInfo.getPort().name();
- if (!TextUtils.isEmpty(address)) {
+ if (mBluetoothRouteController.containsBondedDeviceWithAddress(address)) {
routeId = mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
deviceName = mBluetoothRouteController.getNameForBluetoothAddress(address);
}
@@ -661,8 +668,6 @@ import java.util.Objects;
MediaRoute2Info.TYPE_HDMI_EARC,
/* defaultRouteId= */ "ROUTE_ID_HDMI_EARC",
/* nameResource= */ R.string.default_audio_route_name_external_device));
- // TODO: b/305199571 - Add a proper type constants and human readable names for AUX_LINE,
- // LINE_ANALOG, LINE_DIGITAL, BLE_BROADCAST, BLE_SPEAKER, BLE_HEADSET, and HEARING_AID.
AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
AudioDeviceInfo.TYPE_HEARING_AID,
new SystemRouteInfo(
@@ -690,19 +695,22 @@ import java.util.Objects;
AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
AudioDeviceInfo.TYPE_LINE_DIGITAL,
new SystemRouteInfo(
- MediaRoute2Info.TYPE_UNKNOWN,
+ com.android.media.flags.Flags.enableNewWiredMediaRoute2InfoTypes()
+ ? MediaRoute2Info.TYPE_LINE_DIGITAL : MediaRoute2Info.TYPE_UNKNOWN,
/* defaultRouteId= */ "ROUTE_ID_LINE_DIGITAL",
/* nameResource= */ R.string.default_audio_route_name_external_device));
AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
AudioDeviceInfo.TYPE_LINE_ANALOG,
new SystemRouteInfo(
- MediaRoute2Info.TYPE_UNKNOWN,
+ com.android.media.flags.Flags.enableNewWiredMediaRoute2InfoTypes()
+ ? MediaRoute2Info.TYPE_LINE_ANALOG : MediaRoute2Info.TYPE_UNKNOWN,
/* defaultRouteId= */ "ROUTE_ID_LINE_ANALOG",
/* nameResource= */ R.string.default_audio_route_name_external_device));
AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
AudioDeviceInfo.TYPE_AUX_LINE,
new SystemRouteInfo(
- MediaRoute2Info.TYPE_UNKNOWN,
+ com.android.media.flags.Flags.enableNewWiredMediaRoute2InfoTypes()
+ ? MediaRoute2Info.TYPE_AUX_LINE : MediaRoute2Info.TYPE_UNKNOWN,
/* defaultRouteId= */ "ROUTE_ID_AUX_LINE",
/* nameResource= */ R.string.default_audio_route_name_external_device));
AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
@@ -711,5 +719,13 @@ import java.util.Objects;
MediaRoute2Info.TYPE_DOCK,
/* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG",
/* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+ if (Flags.enableMultichannelGroupDevice()) {
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_MULTICHANNEL_GROUP,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_MULTICHANNEL_SPEAKER_GROUP,
+ /* defaultRouteId= */ "ROUTE_ID_MULTICHANNEL_SPEAKER_GROUP",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ }
}
}
diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
index 8b65ea305ad8..c79d6e5400cd 100644
--- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
+++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
@@ -141,6 +141,11 @@ import java.util.stream.Collectors;
mContext.unregisterReceiver(mDeviceStateChangedReceiver);
}
+ /** Returns true if the given address corresponds to a currently-bonded Bluetooth device. */
+ public synchronized boolean containsBondedDeviceWithAddress(@Nullable String address) {
+ return mAddressToBondedDevice.containsKey(address);
+ }
+
@Nullable
public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) {
BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index b0fa523da959..58c8450d714d 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -42,15 +42,16 @@ abstract class MediaRoute2Provider {
final Object mLock = new Object();
Callback mCallback;
- boolean mIsSystemRouteProvider;
+ public final boolean mIsSystemRouteProvider;
private volatile MediaRoute2ProviderInfo mProviderInfo;
@GuardedBy("mLock")
final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>();
- MediaRoute2Provider(@NonNull ComponentName componentName) {
+ MediaRoute2Provider(@NonNull ComponentName componentName, boolean isSystemRouteProvider) {
mComponentName = Objects.requireNonNull(componentName, "Component name must not be null.");
mUniqueId = componentName.flattenToShortString();
+ mIsSystemRouteProvider = isSystemRouteProvider;
}
public void setCallback(Callback callback) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 56b93e8ded82..5ee9452c6a53 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -66,6 +66,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
private final int mUserId;
private final Handler mHandler;
private final boolean mIsSelfScanOnlyProvider;
+ private final boolean mSupportsSystemMediaRouting;
private final ServiceConnection mServiceConnection = new ServiceConnectionImpl();
// Connection state
@@ -95,12 +96,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
@NonNull Looper looper,
@NonNull ComponentName componentName,
boolean isSelfScanOnlyProvider,
+ boolean supportsSystemMediaRouting,
int userId) {
- super(componentName);
+ super(componentName, /* isSystemRouteProvider= */ false);
mContext = Objects.requireNonNull(context, "Context must not be null.");
mRequestIdToSessionCreationRequest = new LongSparseArray<>();
mSessionOriginalIdToTransferRequest = new HashMap<>();
mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
+ mSupportsSystemMediaRouting = supportsSystemMediaRouting;
mUserId = userId;
mHandler = new Handler(looper);
}
@@ -651,11 +654,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
}
return TextUtils.formatSimple(
"ProviderServiceProxy - package: %s, bound: %b, connection (active:%b, ready:%b), "
- + "pending (session creations: %d, transfers: %d)",
+ + "system media=%b, pending (session creations: %d, transfers: %d)",
mComponentName.getPackageName(),
mBound,
mActiveConnection != null,
mConnectionReady,
+ mSupportsSystemMediaRouting,
pendingSessionCreationCount,
pendingTransferCount);
}
@@ -697,7 +701,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
Connection(IMediaRoute2ProviderService serviceBinder) {
mService = serviceBinder;
- mCallbackStub = new ServiceCallbackStub(this);
+ mCallbackStub = new ServiceCallbackStub(this, mSupportsSystemMediaRouting);
}
public boolean register() {
@@ -811,9 +815,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
private static final class ServiceCallbackStub extends
IMediaRoute2ProviderServiceCallback.Stub {
private final WeakReference<Connection> mConnectionRef;
+ private final boolean mAllowSystemMediaRoutes;
- ServiceCallbackStub(Connection connection) {
+ ServiceCallbackStub(Connection connection, boolean allowSystemMediaRoutes) {
mConnectionRef = new WeakReference<>(connection);
+ mAllowSystemMediaRoutes = allowSystemMediaRoutes;
}
public void dispose() {
@@ -846,6 +852,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
+ "Disallowed route: "
+ route);
}
+
+ if (route.supportsSystemMediaRouting() && !mAllowSystemMediaRoutes) {
+ throw new SecurityException(
+ "This provider is not allowed to publish routes that support system"
+ + " media routing. Disallowed route: "
+ + route);
+ }
}
Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 93ef6f044e1b..69c460e0f19d 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -17,7 +17,9 @@
package com.android.server.media;
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.Manifest;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -130,22 +132,33 @@ final class MediaRoute2ProviderWatcher {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo != null) {
boolean isSelfScanOnlyProvider = false;
+ boolean supportsSystemMediaRouting = false;
Iterator<String> categoriesIterator = resolveInfo.filter.categoriesIterator();
if (categoriesIterator != null) {
while (categoriesIterator.hasNext()) {
+ String category = categoriesIterator.next();
isSelfScanOnlyProvider |=
- MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(
- categoriesIterator.next());
+ MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(category);
+ supportsSystemMediaRouting |=
+ MediaRoute2ProviderService.SERVICE_INTERFACE_SYSTEM_MEDIA.equals(
+ category);
}
}
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
+ supportsSystemMediaRouting &= Flags.enableMirroringInMediaRouter2();
+ supportsSystemMediaRouting &=
+ mPackageManager.checkPermission(
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ serviceInfo.packageName)
+ == PERMISSION_GRANTED;
MediaRoute2ProviderServiceProxy proxy =
new MediaRoute2ProviderServiceProxy(
mContext,
mHandler.getLooper(),
new ComponentName(serviceInfo.packageName, serviceInfo.name),
isSelfScanOnlyProvider,
+ supportsSystemMediaRouting,
mUserId);
Slog.i(
TAG,
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e1b8e9f559ed..abc067d4aa9c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -246,7 +246,7 @@ class MediaRouter2ServiceImpl {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
if (hasSystemRoutingPermissions) {
MediaRoute2ProviderInfo providerInfo =
- userRecord.mHandler.mSystemProvider.getProviderInfo();
+ userRecord.mHandler.getSystemProvider().getProviderInfo();
if (providerInfo != null) {
systemRoutes = providerInfo.getRoutes();
} else {
@@ -258,7 +258,8 @@ class MediaRouter2ServiceImpl {
}
} else {
systemRoutes = new ArrayList<>();
- systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
+ systemRoutes.add(
+ userRecord.mHandler.getSystemProvider().getDefaultRoute());
}
}
return new ArrayList<>(systemRoutes);
@@ -677,24 +678,15 @@ class MediaRouter2ServiceImpl {
@NonNull IMediaRouter2Manager manager,
int requestId,
@NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route,
- @NonNull UserHandle transferInitiatorUserHandle,
- @NonNull String transferInitiatorPackageName) {
+ @NonNull MediaRoute2Info route) {
Objects.requireNonNull(manager, "manager must not be null");
Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
- Objects.requireNonNull(transferInitiatorUserHandle);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithManagerLocked(
- requestId,
- manager,
- oldSession,
- route,
- transferInitiatorUserHandle,
- transferInitiatorPackageName);
+ requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -859,10 +851,10 @@ class MediaRouter2ServiceImpl {
if (setDeviceRouteSelected) {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
- return userRecord.mHandler.mSystemProvider
+ return userRecord.mHandler.getSystemProvider()
.generateDeviceRouteSelectedSessionInfo(targetPackageName);
} else {
- sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+ sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
if (!sessionInfos.isEmpty()) {
// Return a copy of the current system session with no modification,
// except setting the client package name.
@@ -875,7 +867,7 @@ class MediaRouter2ServiceImpl {
}
} else {
return new RoutingSessionInfo.Builder(
- userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
+ userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
.setClientPackageName(targetPackageName)
.build();
}
@@ -1112,19 +1104,12 @@ class MediaRouter2ServiceImpl {
continue;
}
- Log.w(
- TAG,
- TextUtils.formatSimple(
- "Revoking access to manager record id: %d, package: %s, userId:"
- + " %d",
- manager.mManagerId, manager.mOwnerPackageName, userRecord.mUserId));
-
+ Slog.w(TAG, "Revoking access for " + manager.getDebugString());
unregisterManagerLocked(manager.mManager, /* died */ false);
-
try {
manager.mManager.invalidateInstance();
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify manager= " + manager + " of permission revocation.");
+ manager.logRemoteException("invalidateInstance", ex);
}
}
}
@@ -1152,6 +1137,7 @@ class MediaRouter2ServiceImpl {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
RouterRecord routerRecord =
new RouterRecord(
+ mContext,
userRecord,
router,
uid,
@@ -1390,7 +1376,7 @@ class MediaRouter2ServiceImpl {
}
manager.mLastSessionCreationRequest = null;
} else {
- String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
+ String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -1478,7 +1464,7 @@ class MediaRouter2ServiceImpl {
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
UserHandler userHandler = routerRecord.mUserRecord.mHandler;
- String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
+ String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -1653,9 +1639,11 @@ class MediaRouter2ServiceImpl {
manager));
}
+ List<MediaRoute2Info> routes =
+ userRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters.values().stream()
+ .toList();
userRecord.mHandler.sendMessage(
- obtainMessage(
- UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
+ obtainMessage(ManagerRecord::notifyRoutesUpdated, managerRecord, routes));
}
@GuardedBy("mLock")
@@ -1743,9 +1731,7 @@ class MediaRouter2ServiceImpl {
int requestId,
@NonNull IMediaRouter2Manager manager,
@NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route,
- @NonNull UserHandle transferInitiatorUserHandle,
- @NonNull String transferInitiatorPackageName) {
+ @NonNull MediaRoute2Info route) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord == null) {
return;
@@ -2071,6 +2057,7 @@ class MediaRouter2ServiceImpl {
}
final class RouterRecord implements IBinder.DeathRecipient {
+ public final Context mContext;
public final UserRecord mUserRecord;
public final String mPackageName;
public final List<Integer> mSelectRouteSequenceNumbers;
@@ -2089,6 +2076,7 @@ class MediaRouter2ServiceImpl {
@Nullable public RouteListingPreference mRouteListingPreference;
RouterRecord(
+ Context context,
UserRecord userRecord,
IMediaRouter2 router,
int uid,
@@ -2098,6 +2086,7 @@ class MediaRouter2ServiceImpl {
boolean hasModifyAudioRoutingPermission,
boolean hasMediaContentControlPermission,
boolean hasMediaRoutingControl) {
+ mContext = context;
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
@@ -2141,11 +2130,11 @@ class MediaRouter2ServiceImpl {
notifyRoutesUpdated(routesToReport.values().stream().toList());
List<RoutingSessionInfo> sessionInfos =
- mUserRecord.mHandler.mSystemProvider.getSessionInfos();
+ mUserRecord.mHandler.getSystemProvider().getSessionInfos();
RoutingSessionInfo systemSessionToReport =
newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
? sessionInfos.get(0)
- : mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
+ : mUserRecord.mHandler.getSystemProvider().getDefaultSessionInfo();
notifySessionInfoChanged(systemSessionToReport);
}
}
@@ -2295,7 +2284,7 @@ class MediaRouter2ServiceImpl {
if (route.isSystemRoute() && !hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system
// route info from them.
- route = mUserRecord.mHandler.mSystemProvider.getDefaultRoute();
+ route = mUserRecord.mHandler.getSystemProvider().getDefaultRoute();
}
mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
} catch (RemoteException ex) {
@@ -2338,18 +2327,45 @@ class MediaRouter2ServiceImpl {
}
/**
- * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
- * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
+ * Returns a filtered copy of {@code routes} that contains only the routes that are visible
+ * to this RouterRecord.
*/
private List<MediaRoute2Info> getVisibleRoutes(@NonNull List<MediaRoute2Info> routes) {
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
for (MediaRoute2Info route : routes) {
- if (route.isVisibleTo(mPackageName)) {
+ if (route.isVisibleTo(mPackageName) && hasPermissionsToSeeRoute(route)) {
filteredRoutes.add(route);
}
}
return filteredRoutes;
}
+
+ /**
+ * @return whether this RouterRecord has the required permissions to see the given route.
+ */
+ private boolean hasPermissionsToSeeRoute(MediaRoute2Info route) {
+ if (!Flags.enableRouteVisibilityControlApi()) {
+ return true;
+ }
+ List<Set<String>> permissionSets = route.getRequiredPermissions();
+ if (permissionSets.isEmpty()) {
+ return true;
+ }
+ for (Set<String> permissionSet : permissionSets) {
+ boolean hasAllInSet = true;
+ for (String permission : permissionSet) {
+ if (mContext.checkPermission(permission, mPid, mUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ hasAllInSet = false;
+ break;
+ }
+ }
+ if (hasAllInSet) {
+ return true;
+ }
+ }
+ return false;
+ }
}
final class ManagerRecord implements IBinder.DeathRecipient {
@@ -2426,13 +2442,57 @@ class MediaRouter2ServiceImpl {
try {
mManager.notifyRequestFailed(requestId, reason);
} catch (RemoteException ex) {
- Slog.w(
- TAG,
- "Failed to notify manager of the request failure. Manager probably died.",
- ex);
+ logRemoteException("notifyRequestFailed", ex);
+ }
+ }
+
+ /**
+ * Notifies the corresponding manager of the availability of the given routes.
+ *
+ * @param routes The routes available to the manager that corresponds to this record.
+ */
+ public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+ try {
+ mManager.notifyRoutesUpdated(routes);
+ } catch (RemoteException ex) {
+ logRemoteException("notifyRoutesUpdated", ex);
+ }
+ }
+
+ /**
+ * Notifies the corresponding manager of an update in the given session.
+ *
+ * @param sessionInfo The updated session info.
+ */
+ public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+ try {
+ mManager.notifySessionUpdated(sessionInfo);
+ } catch (RemoteException ex) {
+ logRemoteException("notifySessionUpdated", ex);
+ }
+ }
+
+ /**
+ * Notifies the corresponding manager that the given session has been released.
+ *
+ * @param sessionInfo The released session info.
+ */
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+ try {
+ mManager.notifySessionReleased(sessionInfo);
+ } catch (RemoteException ex) {
+ logRemoteException("notifySessionReleased", ex);
}
}
+ private void logRemoteException(String operation, RemoteException exception) {
+ String message =
+ TextUtils.formatSimple(
+ "%s failed for %s due to %s",
+ operation, getDebugString(), exception.toString());
+ Slog.w(TAG, message);
+ }
+
private void updateScanningState(@ScanningState int scanningState) {
if (mScanningState == scanningState) {
return;
@@ -2445,9 +2505,16 @@ class MediaRouter2ServiceImpl {
UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
}
- @Override
- public String toString() {
- return "Manager " + mOwnerPackageName + " (pid " + mOwnerPid + ")";
+ /** Returns a human readable representation of this manager record for logging purposes. */
+ public String getDebugString() {
+ return TextUtils.formatSimple(
+ "Manager %s (id=%d,pid=%d,userId=%d,uid=%d,targetPkg=%s)",
+ mOwnerPackageName,
+ mManagerId,
+ mOwnerPid,
+ mUserRecord.mUserId,
+ mOwnerUid,
+ mTargetPackageName);
}
}
@@ -2500,6 +2567,10 @@ class MediaRouter2ServiceImpl {
private boolean mRunning;
+ private SystemMediaRoute2Provider getSystemProvider() {
+ return mSystemProvider;
+ }
+
// TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
UserHandler(
@NonNull MediaRouter2ServiceImpl service,
@@ -2509,21 +2580,24 @@ class MediaRouter2ServiceImpl {
mServiceRef = new WeakReference<>(service);
mUserRecord = userRecord;
mSystemProvider =
- new SystemMediaRoute2Provider(
- service.mContext, UserHandle.of(userRecord.mUserId), looper);
- mRouteProviders.add(mSystemProvider);
+ Flags.enableMirroringInMediaRouter2()
+ ? new SystemMediaRoute2Provider2(
+ service.mContext, UserHandle.of(userRecord.mUserId), looper)
+ : new SystemMediaRoute2Provider(
+ service.mContext, UserHandle.of(userRecord.mUserId), looper);
+ mRouteProviders.add(getSystemProvider());
mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
this, mUserRecord.mUserId);
}
void init() {
- mSystemProvider.setCallback(this);
+ getSystemProvider().setCallback(this);
}
private void start() {
if (!mRunning) {
mRunning = true;
- mSystemProvider.start();
+ getSystemProvider().start();
mWatcher.start();
}
}
@@ -2532,7 +2606,7 @@ class MediaRouter2ServiceImpl {
if (mRunning) {
mRunning = false;
mWatcher.stop(); // also stops all providers
- mSystemProvider.stop();
+ getSystemProvider().stop();
}
}
@@ -2624,7 +2698,7 @@ class MediaRouter2ServiceImpl {
String indent = prefix + " ";
pw.println(indent + "mRunning=" + mRunning);
- mSystemProvider.dump(pw, prefix);
+ getSystemProvider().dump(pw, prefix);
mWatcher.dump(pw, prefix);
}
@@ -2717,7 +2791,7 @@ class MediaRouter2ServiceImpl {
hasAddedOrModifiedRoutes,
hasRemovedRoutes,
provider.mIsSystemRouteProvider,
- mSystemProvider.getDefaultRoute());
+ getSystemProvider().getDefaultRoute());
}
private static String getPackageNameFromNullableRecord(
@@ -2761,18 +2835,20 @@ class MediaRouter2ServiceImpl {
getRouterRecords(/* hasSystemRoutingPermission= */ true);
List<RouterRecord> routerRecordsWithoutSystemRoutingPermission =
getRouterRecords(/* hasSystemRoutingPermission= */ false);
- List<IMediaRouter2Manager> managers = getManagers();
+ List<ManagerRecord> managers = getManagerRecords();
// Managers receive all provider updates with all routes.
- notifyRoutesUpdatedToManagers(
- managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+ List<MediaRoute2Info> routesForPrivilegedRouters =
+ mLastNotifiedRoutesToPrivilegedRouters.values().stream().toList();
+ for (ManagerRecord manager : managers) {
+ manager.notifyRoutesUpdated(routesForPrivilegedRouters);
+ }
// Routers with system routing access (either via {@link MODIFY_AUDIO_ROUTING} or
// {@link BLUETOOTH_CONNECT} + {@link BLUETOOTH_SCAN}) receive all provider updates
// with all routes.
notifyRoutesUpdatedToRouterRecords(
- routerRecordsWithSystemRoutingPermission,
- new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+ routerRecordsWithSystemRoutingPermission, routesForPrivilegedRouters);
if (!isSystemProvider) {
// Regular routers receive updates from all non-system providers with all non-system
@@ -2929,7 +3005,8 @@ class MediaRouter2ServiceImpl {
}
// Bypass checking router if it's the system session (routerRecord should be null)
- if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
+ if (TextUtils.equals(
+ getProviderId(uniqueSessionId), getSystemProvider().getUniqueId())) {
return true;
}
@@ -3060,7 +3137,7 @@ class MediaRouter2ServiceImpl {
&& !matchingRequest.mRouterRecord.hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system routing
// session info from them.
- sessionInfo = mSystemProvider.getDefaultSessionInfo();
+ sessionInfo = getSystemProvider().getDefaultSessionInfo();
}
matchingRequest.mRouterRecord.notifySessionCreated(
toOriginalRequestId(uniqueRequestId), sessionInfo);
@@ -3068,17 +3145,19 @@ class MediaRouter2ServiceImpl {
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
- List<IMediaRouter2Manager> managers = getManagers();
- notifySessionUpdatedToManagers(managers, sessionInfo);
+ List<ManagerRecord> managers = getManagerRecords();
+ for (ManagerRecord manager : managers) {
+ manager.notifySessionUpdated(sessionInfo);
+ }
// For system provider, notify all routers.
- if (provider == mSystemProvider) {
+ if (provider == getSystemProvider()) {
if (mServiceRef.get() == null) {
return;
}
notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
notifySessionInfoChangedToRouters(
- getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
+ getRouterRecords(false), getSystemProvider().getDefaultSessionInfo());
return;
}
@@ -3093,8 +3172,10 @@ class MediaRouter2ServiceImpl {
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
- List<IMediaRouter2Manager> managers = getManagers();
- notifySessionReleasedToManagers(managers, sessionInfo);
+ List<ManagerRecord> managers = getManagerRecords();
+ for (ManagerRecord manager : managers) {
+ manager.notifySessionReleased(sessionInfo);
+ }
RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
if (routerRecord == null) {
@@ -3169,20 +3250,6 @@ class MediaRouter2ServiceImpl {
return true;
}
- private List<IMediaRouter2Manager> getManagers() {
- final List<IMediaRouter2Manager> managers = new ArrayList<>();
- MediaRouter2ServiceImpl service = mServiceRef.get();
- if (service == null) {
- return managers;
- }
- synchronized (service.mLock) {
- for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
- managers.add(managerRecord.mManager);
- }
- }
- return managers;
- }
-
private List<RouterRecord> getRouterRecords() {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
@@ -3226,7 +3293,8 @@ class MediaRouter2ServiceImpl {
MediaRoute2ProviderInfo systemProviderInfo = null;
for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
// TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
- if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
+ if (TextUtils.equals(
+ providerInfo.getUniqueId(), getSystemProvider().getUniqueId())) {
// Adding routes from system provider will be handled below, so skip it here.
systemProviderInfo = providerInfo;
continue;
@@ -3242,10 +3310,10 @@ class MediaRouter2ServiceImpl {
// This shouldn't happen.
Slog.wtf(TAG, "System route provider not found.");
}
- currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0);
+ currentSystemSessionInfo = getSystemProvider().getSessionInfos().get(0);
} else {
- currentRoutes.add(mSystemProvider.getDefaultRoute());
- currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
+ currentRoutes.add(getSystemProvider().getDefaultRoute());
+ currentSystemSessionInfo = getSystemProvider().getDefaultSessionInfo();
}
if (!currentRoutes.isEmpty()) {
@@ -3269,37 +3337,6 @@ class MediaRouter2ServiceImpl {
}
}
- /**
- * Notifies {@code manager} with all known routes. This only happens once after {@code
- * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
- * registerManager()}.
- *
- * @param manager {@link IMediaRouter2Manager} to be notified.
- */
- private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
- if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
- return;
- }
- try {
- manager.notifyRoutesUpdated(
- new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
- }
- }
-
- private void notifyRoutesUpdatedToManagers(
- @NonNull List<IMediaRouter2Manager> managers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifyRoutesUpdated(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
- }
- }
- }
-
private void notifySessionCreatedToManagers(long managerRequestId,
@NonNull RoutingSessionInfo session) {
int requesterId = toRequesterId(managerRequestId);
@@ -3317,32 +3354,6 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifySessionUpdatedToManagers(
- @NonNull List<IMediaRouter2Manager> managers,
- @NonNull RoutingSessionInfo sessionInfo) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifySessionUpdated(sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "notifySessionUpdatedToManagers: "
- + "Failed to notify. Manager probably died.", ex);
- }
- }
- }
-
- private void notifySessionReleasedToManagers(
- @NonNull List<IMediaRouter2Manager> managers,
- @NonNull RoutingSessionInfo sessionInfo) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifySessionReleased(sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "notifySessionReleasedToManagers: "
- + "Failed to notify. Manager probably died.", ex);
- }
- }
- }
-
private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
@NonNull IMediaRouter2Manager manager) {
try {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 363b8e4228b0..68e195d7f079 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -607,16 +607,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
IMediaRouter2Manager manager,
int requestId,
RoutingSessionInfo oldSession,
- MediaRoute2Info route,
- UserHandle transferInitiatorUserHandle,
- String transferInitiatorPackageName) {
- mService2.requestCreateSessionWithManager(
- manager,
- requestId,
- oldSession,
- route,
- transferInitiatorUserHandle,
- transferInitiatorPackageName);
+ MediaRoute2Info route) {
+ mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
}
// Binder call
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index c8a87994ee16..e7b79abed73a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.Notification;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
@@ -98,11 +97,8 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
}
@Override
- public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
- // For an app to be eligible for FGS delegation, it needs a media session liked to a media
- // notification. Currently, notifications cannot be linked to MediaSession2 so it is not
- // supported.
- return null;
+ public boolean hasLinkedNotificationSupport() {
+ return false;
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0d779af22c2d..5f7c86f7b670 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
@@ -30,7 +29,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
@@ -48,9 +46,7 @@ import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaMetadata;
-import android.media.MediaRouter2Manager;
import android.media.Rating;
-import android.media.RoutingSessionInfo;
import android.media.VolumeProvider;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -186,9 +182,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private final MediaSessionService mService;
private final UriGrantsManagerInternal mUgmInternal;
private final Context mContext;
- private final boolean mVolumeAdjustmentForRemoteGroupSessions;
-
- private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
private final Object mLock = new Object();
// This field is partially guarded by mLock. Writes and non-atomic iterations (for example:
@@ -309,35 +302,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
- mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
-
- mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
- private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
- return new ForegroundServiceDelegationOptions.Builder()
- .setClientPid(mOwnerPid)
- .setClientUid(getUid())
- .setClientPackageName(getPackageName())
- .setClientAppThread(null)
- .setSticky(false)
- .setClientInstanceName(
- "MediaSessionFgsDelegate_"
- + getUid()
- + "_"
- + mOwnerPid
- + "_"
- + getPackageName())
- .setForegroundServiceTypes(0)
- .setDelegationService(
- ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
- .build();
- }
-
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -395,6 +364,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return mUserId;
}
+ @Override
+ public boolean hasLinkedNotificationSupport() {
+ return true;
+ }
+
/**
* Check if this session has system priorty and should receive media buttons
* before any other sessions.
@@ -676,49 +650,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
return false;
}
- if (mVolumeAdjustmentForRemoteGroupSessions) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Volume adjustment for remote group sessions allowed so MediaSessionRecord"
- + " can handle volume key");
- }
- return true;
- }
- // See b/228021646 for details.
- MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
- List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName);
- boolean foundNonSystemSession = false;
- boolean remoteSessionAllowVolumeAdjustment = true;
- if (DEBUG) {
- Slog.d(
- TAG,
- "Found "
- + sessions.size()
- + " routing sessions for package name "
- + mPackageName);
- }
- for (RoutingSessionInfo session : sessions) {
- if (DEBUG) {
- Slog.d(TAG, "Found routingSessionInfo: " + session);
- }
- if (!session.isSystemSession()) {
- foundNonSystemSession = true;
- if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) {
- remoteSessionAllowVolumeAdjustment = false;
- }
- }
- }
- if (!foundNonSystemSession) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Package " + mPackageName
- + " has a remote media session but no associated routing session");
- }
- }
-
- return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
+ return true;
}
@Override
@@ -800,11 +732,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return mPackageName + "/" + mTag + "/" + getUniqueId() + " (userId=" + mUserId + ")";
}
- @Override
- public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
- return mForegroundServiceDelegationOptions;
- }
-
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 6c3b1234935a..396660816b45 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,10 +16,8 @@
package com.android.server.media;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.Notification;
import android.media.AudioManager;
-import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -63,13 +61,14 @@ public abstract class MediaSessionRecordImpl {
public abstract int getUserId();
/**
- * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
- * service with changes in the {@link PlaybackState} for this session.
+ * Returns whether this session supports linked notifications.
*
- * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
- * manager service with changes in the {@link PlaybackState} for this session.
+ * <p>A notification is linked to a media session if it contains
+ * {@link android.app.Notification#EXTRA_MEDIA_SESSION}.
+ *
+ * @return {@code true} if this session supports linked notifications, {@code false} otherwise.
*/
- public abstract ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+ public abstract boolean hasLinkedNotificationSupport();
/**
* Check if this session has system priority and should receive media buttons before any other
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2b29fbd9c5b5..e091fc62451b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,7 +30,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -185,9 +184,9 @@ public class MediaSessionService extends SystemService implements Monitor {
/**
* Maps uid with all user engaged session records associated to it. It's used for calling
- * ActivityManagerInternal startFGS and stopFGS. This collection doesn't contain
- * MediaSession2Record(s). When the media session is paused, There exists a timeout before
- * calling stopFGS unlike usage logging which considers it disengaged immediately.
+ * ActivityManagerInternal internal api to set fgs active/inactive. This collection doesn't
+ * contain MediaSession2Record(s). When the media session is paused, There exists a timeout
+ * before setting FGS inactive unlike usage logging which considers it disengaged immediately.
*/
@GuardedBy("mLock")
private final Map<Integer, Set<MediaSessionRecordImpl>> mUserEngagedSessionsForFgs =
@@ -195,7 +194,7 @@ public class MediaSessionService extends SystemService implements Monitor {
/* Maps uid with all media notifications associated to it */
@GuardedBy("mLock")
- private final Map<Integer, Set<Notification>> mMediaNotifications = new HashMap<>();
+ private final Map<Integer, Set<StatusBarNotification>> mMediaNotifications = new HashMap<>();
/**
* Holds all {@link MediaSessionRecordImpl} which we've reported as being {@link
@@ -700,10 +699,10 @@ public class MediaSessionService extends SystemService implements Monitor {
MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
if (isUserEngaged) {
addUserEngagedSession(mediaSessionRecord);
- startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
+ setFgsActiveIfSessionIsLinkedToNotification(mediaSessionRecord);
} else {
removeUserEngagedSession(mediaSessionRecord);
- stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
+ setFgsInactiveIfNoSessionIsLinkedToNotification(mediaSessionRecord);
}
}
@@ -737,17 +736,20 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- private void startFgsIfSessionIsLinkedToNotification(
+ private void setFgsActiveIfSessionIsLinkedToNotification(
MediaSessionRecordImpl mediaSessionRecord) {
- Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ Log.d(TAG, "setFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
return;
}
+ if (!mediaSessionRecord.hasLinkedNotificationSupport()) {
+ return;
+ }
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- startFgsDelegateLocked(mediaSessionRecord);
+ for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (mediaSessionRecord.isLinkedToNotification(sbn.getNotification())) {
+ setFgsActiveLocked(mediaSessionRecord, sbn);
return;
}
}
@@ -755,81 +757,92 @@ public class MediaSessionService extends SystemService implements Monitor {
}
@GuardedBy("mLock")
- private void startFgsDelegateLocked(MediaSessionRecordImpl mediaSessionRecord) {
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return; // This record doesn't support FGS. Typically a MediaSession2 record.
- }
+ private void setFgsActiveLocked(MediaSessionRecordImpl mediaSessionRecord,
+ StatusBarNotification sbn) {
if (!mFgsAllowedMediaSessionRecords.add(mediaSessionRecord)) {
- return; // This record is already FGS-started.
+ return; // This record already is FGS-activated.
}
final long token = Binder.clearCallingIdentity();
try {
+ final String packageName = sbn.getPackageName();
+ final int uid = sbn.getUid();
+ final int notificationId = sbn.getId();
Log.i(
TAG,
TextUtils.formatSimple(
- "startFgsDelegate: pkg=%s uid=%d",
- foregroundServiceDelegationOptions.mClientPackageName,
- foregroundServiceDelegationOptions.mClientUid));
- mActivityManagerInternal.startForegroundServiceDelegate(
- foregroundServiceDelegationOptions, /* connection= */ null);
+ "setFgsActiveLocked: pkg=%s uid=%d notification=%d",
+ packageName, uid, notificationId));
+ mActivityManagerInternal.notifyActiveMediaForegroundService(packageName,
+ sbn.getUser().getIdentifier(), notificationId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- private void stopFgsIfNoSessionIsLinkedToNotification(
+ @Nullable
+ private StatusBarNotification getLinkedNotification(
+ int uid, MediaSessionRecordImpl record) {
+ synchronized (mLock) {
+ for (StatusBarNotification sbn :
+ mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (record.isLinkedToNotification(sbn.getNotification())) {
+ return sbn;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void setFgsInactiveIfNoSessionIsLinkedToNotification(
MediaSessionRecordImpl mediaSessionRecord) {
- Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ Log.d(TAG, "setFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
return;
}
+ if (!mediaSessionRecord.hasLinkedNotificationSupport()) {
+ return;
+ }
synchronized (mLock) {
- int uid = mediaSessionRecord.getUid();
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return;
- }
-
+ final int uid = mediaSessionRecord.getUid();
for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification :
+ for (StatusBarNotification sbn :
mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (record.isLinkedToNotification(mediaNotification)) {
+ if (record.isLinkedToNotification(sbn.getNotification())) {
// A user engaged session linked with a media notification is found.
// We shouldn't call stop FGS in this case.
return;
}
}
}
-
- stopFgsDelegateLocked(mediaSessionRecord);
+ final StatusBarNotification linkedNotification =
+ getLinkedNotification(uid, mediaSessionRecord);
+ if (linkedNotification != null) {
+ setFgsInactiveLocked(mediaSessionRecord, linkedNotification);
+ }
}
}
@GuardedBy("mLock")
- private void stopFgsDelegateLocked(MediaSessionRecordImpl mediaSessionRecord) {
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return; // This record doesn't support FGS. Typically a MediaSession2 record.
- }
+ private void setFgsInactiveLocked(MediaSessionRecordImpl mediaSessionRecord,
+ StatusBarNotification sbn) {
if (!mFgsAllowedMediaSessionRecords.remove(mediaSessionRecord)) {
- return; // This record is not FGS-started. No need to stop it.
+ return; // This record is not FGS-active. No need to set inactive.
}
final long token = Binder.clearCallingIdentity();
try {
+ final String packageName = sbn.getPackageName();
+ final int userId = sbn.getUser().getIdentifier();
+ final int uid = sbn.getUid();
+ final int notificationId = sbn.getId();
Log.i(
TAG,
TextUtils.formatSimple(
- "stopFgsDelegate: pkg=%s uid=%d",
- foregroundServiceDelegationOptions.mClientPackageName,
- foregroundServiceDelegationOptions.mClientUid));
- mActivityManagerInternal.stopForegroundServiceDelegate(
- foregroundServiceDelegationOptions);
+ "setFgsInactiveLocked: pkg=%s uid=%d notification=%d",
+ packageName, uid, notificationId));
+ mActivityManagerInternal.notifyInactiveMediaForegroundService(packageName,
+ userId, notificationId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3259,18 +3272,18 @@ public class MediaSessionService extends SystemService implements Monitor {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
- Notification postedNotification = sbn.getNotification();
int uid = sbn.getUid();
+ final Notification postedNotification = sbn.getNotification();
if (!postedNotification.isMediaNotification()) {
return;
}
synchronized (mLock) {
mMediaNotifications.putIfAbsent(uid, new HashSet<>());
- mMediaNotifications.get(uid).add(postedNotification);
+ mMediaNotifications.get(uid).add(sbn);
for (MediaSessionRecordImpl mediaSessionRecord :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
if (mediaSessionRecord.isLinkedToNotification(postedNotification)) {
- startFgsDelegateLocked(mediaSessionRecord);
+ setFgsActiveLocked(mediaSessionRecord, sbn);
return;
}
}
@@ -3286,9 +3299,9 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
synchronized (mLock) {
- Set<Notification> uidMediaNotifications = mMediaNotifications.get(uid);
+ Set<StatusBarNotification> uidMediaNotifications = mMediaNotifications.get(uid);
if (uidMediaNotifications != null) {
- uidMediaNotifications.remove(removedNotification);
+ uidMediaNotifications.remove(sbn);
if (uidMediaNotifications.isEmpty()) {
mMediaNotifications.remove(uid);
}
@@ -3300,8 +3313,7 @@ public class MediaSessionService extends SystemService implements Monitor {
if (notificationRecord == null) {
return;
}
-
- stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
+ setFgsInactiveIfNoSessionIsLinkedToNotification(notificationRecord);
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8c6273ce959f..5fb80ba92091 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -90,8 +90,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private volatile SessionCreationOrTransferRequest mPendingTransferRequest;
SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) {
- super(COMPONENT_NAME);
- mIsSystemRouteProvider = true;
+ this(context, COMPONENT_NAME, user, looper);
+ }
+
+ protected SystemMediaRoute2Provider(
+ Context context, ComponentName componentName, UserHandle user, Looper looper) {
+ super(componentName, /* isSystemRouteProvider= */ true);
mContext = context;
mUser = user;
mHandler = new Handler(looper);
@@ -589,7 +593,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@Override
protected String getDebugString() {
return TextUtils.formatSimple(
- "SystemMR2Provider - package: %s, selected route id: %s, bluetooth impl: %s",
+ "%s - package: %s, selected route id: %s, bluetooth impl: %s",
+ getClass().getSimpleName(),
mComponentName.getPackageName(),
mSelectedRouteId,
mBluetoothRouteController.getClass().getSimpleName());
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
new file mode 100644
index 000000000000..8a14a3866860
--- /dev/null
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.MediaRoute2ProviderService;
+import android.os.Looper;
+import android.os.UserHandle;
+
+/**
+ * Extends {@link SystemMediaRoute2Provider} by adding system routes provided by {@link
+ * MediaRoute2ProviderService provider services}.
+ *
+ * <p>System routes are those which can handle the system audio and/or video.
+ */
+/* package */ class SystemMediaRoute2Provider2 extends SystemMediaRoute2Provider {
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(
+ SystemMediaRoute2Provider2.class.getPackage().getName(),
+ SystemMediaRoute2Provider2.class.getName());
+
+ SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) {
+ super(context, COMPONENT_NAME, user, looper);
+ }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index db4cdb39e6fc..c428f39fd9d0 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.media.projection;
import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
-import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -40,10 +39,7 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
import android.app.IProcessObserver;
-import android.app.KeyguardManager;
import android.app.compat.CompatChanges;
-import android.app.role.RoleManager;
-import android.companion.AssociationRequest;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -85,7 +81,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.wm.WindowManagerInternal;
@@ -140,12 +135,12 @@ public final class MediaProjectionManagerService extends SystemService
private final ActivityManagerInternal mActivityManagerInternal;
private final PackageManager mPackageManager;
private final WindowManagerInternal mWmInternal;
- private final KeyguardManager mKeyguardManager;
- private final RoleManager mRoleManager;
+
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final MediaProjectionStopController mMediaProjectionStopController;
private MediaRouter.RouteInfo mMediaRouteInfo;
@GuardedBy("mLock")
@@ -175,62 +170,16 @@ public final class MediaProjectionManagerService extends SystemService
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
- mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mKeyguardManager.addKeyguardLockedStateListener(
- mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
- mRoleManager = mContext.getSystemService(RoleManager.class);
+ mMediaProjectionStopController = new MediaProjectionStopController(context,
+ this::maybeStopMediaProjection);
Watchdog.getInstance().addMonitor(this);
}
- /**
- * In order to record the keyguard, the MediaProjection package must be either:
- * - a holder of RECORD_SENSITIVE_CONTENT permission, or
- * - be one of the bugreport allowlisted packages, or
- * - hold the OP_PROJECT_MEDIA AppOp.
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private boolean canCaptureKeyguard() {
- if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
- return true;
- }
- synchronized (mLock) {
- if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
- return false;
- }
- if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
- mProjectionGrant.packageName)
- == PackageManager.PERMISSION_GRANTED) {
- Slog.v(TAG,
- "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
- + "permission");
- return true;
- }
- if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
- mProjectionGrant.uid, mProjectionGrant.packageName, /* attributionTag= */ null,
- "recording lockscreen")) {
- // Some tools use media projection by granting the OP_PROJECT_MEDIA app
- // op via a shell command. Those tools can be granted keyguard capture
- Slog.v(TAG,
- "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
- return true;
- }
- if (isProjectionAppHoldingAppStreamingRoleLocked()) {
- Slog.v(TAG,
- "Allowing keyguard capture for package holding app streaming role.");
- return true;
- }
- return SystemConfig.getInstance().getBugreportWhitelistedPackages()
- .contains(mProjectionGrant.packageName);
- }
- }
-
- @VisibleForTesting
- void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
- if (!isKeyguardLocked) return;
+ private void maybeStopMediaProjection(int reason) {
synchronized (mLock) {
- if (mProjectionGrant != null && !canCaptureKeyguard()) {
- Slog.d(TAG, "Content Recording: Stopped MediaProjection"
- + " due to keyguard lock");
+ if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) {
+ Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
+ + MediaProjectionStopController.stopReasonToString(reason));
mProjectionGrant.stop(StopReason.STOP_DEVICE_LOCKED);
}
}
@@ -300,6 +249,8 @@ public final class MediaProjectionManagerService extends SystemService
}
});
}
+
+ mMediaProjectionStopController.startTrackingStopReasons(mContext);
}
@Override
@@ -665,8 +616,13 @@ public final class MediaProjectionManagerService extends SystemService
// TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
@VisibleForTesting
- MediaProjection createProjectionInternal(int uid, String packageName, int type,
- boolean isPermanentGrant, UserHandle callingUser) {
+ MediaProjection createProjectionInternal(
+ int uid,
+ String packageName,
+ int type,
+ boolean isPermanentGrant,
+ UserHandle callingUser,
+ int displayId) {
MediaProjection projection;
ApplicationInfo ai;
try {
@@ -677,8 +633,14 @@ public final class MediaProjectionManagerService extends SystemService
}
final long callingToken = Binder.clearCallingIdentity();
try {
- projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
- ai.isPrivilegedApp());
+ projection =
+ new MediaProjection(
+ type,
+ uid,
+ packageName,
+ ai.targetSdkVersion,
+ ai.isPrivilegedApp(),
+ displayId);
if (isPermanentGrant) {
mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
@@ -730,20 +692,6 @@ public final class MediaProjectionManagerService extends SystemService
}
}
- /**
- * Application holding the app streaming role
- * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
- * lockscreen.
- *
- * @return true if the is held by the recording application.
- */
- @GuardedBy("mLock")
- private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
- return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
- mContext.getUser())
- .contains(mProjectionGrant.packageName);
- }
-
private void dump(final PrintWriter pw) {
pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
synchronized (mLock) {
@@ -778,11 +726,16 @@ public final class MediaProjectionManagerService extends SystemService
return hasPermission;
}
- @Override // Binder call
- public IMediaProjection createProjection(int processUid, String packageName, int type,
- boolean isPermanentGrant) {
+ // Binder call
+ @Override
+ public IMediaProjection createProjection(
+ int processUid,
+ String packageName,
+ int type,
+ boolean isPermanentGrant,
+ int displayId) {
if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
- != PackageManager.PERMISSION_GRANTED) {
+ != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
+ "projection permission");
}
@@ -790,8 +743,8 @@ public final class MediaProjectionManagerService extends SystemService
throw new IllegalArgumentException("package name must not be empty");
}
final UserHandle callingUser = Binder.getCallingUserHandle();
- return createProjectionInternal(processUid, packageName, type, isPermanentGrant,
- callingUser);
+ return createProjectionInternal(
+ processUid, packageName, type, isPermanentGrant, callingUser, displayId);
}
@Override // Binder call
@@ -953,18 +906,19 @@ public final class MediaProjectionManagerService extends SystemService
public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
requestConsentForInvalidProjection_enforcePermission();
- if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()
- && mKeyguardManager.isKeyguardLocked()) {
- Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked");
- return;
- }
-
synchronized (mLock) {
if (!isCurrentProjection(projection)) {
Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
+ "isn't current");
return;
}
+
+ if (mMediaProjectionStopController.isStartForbidden(mProjectionGrant)) {
+ Slog.v(TAG,
+ "Reusing token: Won't request consent while MediaProjection is "
+ + "restricted");
+ return;
+ }
}
// Remove calling app identity before performing any privileged operations.
@@ -1072,20 +1026,23 @@ public final class MediaProjectionManagerService extends SystemService
}
}
- @VisibleForTesting
final class MediaProjection extends IMediaProjection.Stub {
// Host app has 5 minutes to begin using the token before it is invalid.
// Some apps show a dialog for the user to interact with (selecting recording resolution)
// before starting capture, but after requesting consent.
- final long mDefaultTimeoutMs = Duration.ofMinutes(5).toMillis();
+ final long mDefaultTimeoutMillis = Duration.ofMinutes(5).toMillis();
// The creation timestamp in milliseconds, measured by {@link SystemClock#uptimeMillis}.
- private final long mCreateTimeMs;
+ private final long mCreateTimeMillis;
public final int uid;
public final String packageName;
public final UserHandle userHandle;
private final int mTargetSdkVersion;
private final boolean mIsPrivileged;
private final int mType;
+ // Values for tracking token validity.
+ // Timeout value to compare creation time against.
+ private final long mTimeoutMillis = mDefaultTimeoutMillis;
+ private final int mDisplayId;
private IMediaProjectionCallback mCallback;
private IBinder mToken;
@@ -1094,9 +1051,6 @@ public final class MediaProjectionManagerService extends SystemService
private int mTaskId = -1;
private LaunchCookie mLaunchCookie = null;
- // Values for tracking token validity.
- // Timeout value to compare creation time against.
- private long mTimeoutMs = mDefaultTimeoutMs;
// Count of number of times IMediaProjection#start is invoked.
private int mCountStarts = 0;
// Set if MediaProjection#createVirtualDisplay has been invoked previously (it
@@ -1105,17 +1059,23 @@ public final class MediaProjectionManagerService extends SystemService
// The associated session details already sent to WindowManager.
private ContentRecordingSession mSession;
- MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
- boolean isPrivileged) {
+ MediaProjection(
+ int type,
+ int uid,
+ String packageName,
+ int targetSdkVersion,
+ boolean isPrivileged,
+ int displayId) {
mType = type;
this.uid = uid;
this.packageName = packageName;
userHandle = new UserHandle(UserHandle.getUserId(uid));
mTargetSdkVersion = targetSdkVersion;
mIsPrivileged = isPrivileged;
- mCreateTimeMs = mClock.uptimeMillis();
+ mCreateTimeMillis = mClock.uptimeMillis();
mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(),
MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
+ mDisplayId = displayId;
}
int getVirtualDisplayId() {
@@ -1273,7 +1233,7 @@ public final class MediaProjectionManagerService extends SystemService
}
}
Slog.d(TAG, "Content Recording: handling stopping this projection token"
- + " createTime= " + mCreateTimeMs
+ + " createTime= " + mCreateTimeMillis
+ " countStarts= " + mCountStarts);
stopProjectionLocked(this, stopReason);
mToken.unlinkToDeath(mDeathEater, 0);
@@ -1331,13 +1291,22 @@ public final class MediaProjectionManagerService extends SystemService
return mTaskId;
}
+ @Override // Binder call
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ long getCreateTimeMillis() {
+ return mCreateTimeMillis;
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
@Override
public boolean isValid() {
isValid_enforcePermission();
synchronized (mLock) {
- final long curMs = mClock.uptimeMillis();
- final boolean hasTimedOut = curMs - mCreateTimeMs > mTimeoutMs;
+ final long curMillis = mClock.uptimeMillis();
+ final boolean hasTimedOut = curMillis - mCreateTimeMillis > mTimeoutMillis;
final boolean virtualDisplayCreated = mVirtualDisplayId != INVALID_DISPLAY;
final boolean isValid =
!hasTimedOut && (mCountStarts <= 1) && !virtualDisplayCreated;
@@ -1365,12 +1334,14 @@ public final class MediaProjectionManagerService extends SystemService
@Override
public void notifyVirtualDisplayCreated(int displayId) {
notifyVirtualDisplayCreated_enforcePermission();
- if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) {
- Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection");
- stop(StopReason.STOP_DEVICE_LOCKED);
- return;
- }
synchronized (mLock) {
+ if (mMediaProjectionStopController.isStartForbidden(mProjectionGrant)) {
+ Slog.w(TAG,
+ "Content Recording: MediaProjection start disallowed, aborting "
+ + "MediaProjection");
+ stop(StopReason.STOP_DEVICE_LOCKED);
+ return;
+ }
mVirtualDisplayId = displayId;
// If prior session was does not have a valid display id, then update the display
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
new file mode 100644
index 000000000000..c018e6bc1dc7
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+
+import android.app.AppOpsManager;
+import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemConfig;
+
+import java.util.function.Consumer;
+
+/**
+ * Tracks events that should cause MediaProjection to stop
+ */
+public class MediaProjectionStopController {
+
+ private static final String TAG = "MediaProjectionStopController";
+ @VisibleForTesting
+ static final int STOP_REASON_UNKNOWN = 0;
+ @VisibleForTesting
+ static final int STOP_REASON_KEYGUARD = 1;
+ @VisibleForTesting
+ static final int STOP_REASON_CALL_END = 2;
+
+ private final TelephonyCallback mTelephonyCallback = new ProjectionTelephonyCallback();
+ private final Consumer<Integer> mStopReasonConsumer;
+ private final KeyguardManager mKeyguardManager;
+ private final TelecomManager mTelecomManager;
+ private final TelephonyManager mTelephonyManager;
+ private final AppOpsManager mAppOpsManager;
+ private final PackageManager mPackageManager;
+ private final RoleManager mRoleManager;
+ private final ContentResolver mContentResolver;
+
+ private boolean mIsInCall;
+ private long mLastCallStartTimeMillis;
+
+ public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) {
+ mStopReasonConsumer = stopReasonConsumer;
+ mKeyguardManager = context.getSystemService(KeyguardManager.class);
+ mTelecomManager = context.getSystemService(TelecomManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mPackageManager = context.getPackageManager();
+ mRoleManager = context.getSystemService(RoleManager.class);
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Start tracking stop reasons that may interrupt a MediaProjection session.
+ */
+ public void startTrackingStopReasons(Context context) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mKeyguardManager.addKeyguardLockedStateListener(context.getMainExecutor(),
+ this::onKeyguardLockedStateChanged);
+ if (com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) {
+ callStateChanged();
+ mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(),
+ mTelephonyCallback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Checks whether the given projection grant is exempt from stopping restrictions.
+ */
+ public boolean isExemptFromStopping(
+ MediaProjectionManagerService.MediaProjection projectionGrant, int stopReason) {
+ return isExempt(projectionGrant, stopReason, false);
+ }
+
+ /**
+ * Apps may disregard recording restrictions via MediaProjection for any stop reason if:
+ * - the "Disable Screenshare protections" developer option is enabled
+ * - the app is a holder of RECORD_SENSITIVE_CONTENT permission
+ * - the app holds the OP_PROJECT_MEDIA AppOp
+ * - the app holds the COMPANION_DEVICE_APP_STREAMING role
+ * - the app is one of the bugreport allowlisted packages
+ * - the current projection does not have an active VirtualDisplay associated with the
+ * MediaProjection session
+ */
+ private boolean isExempt(
+ MediaProjectionManagerService.MediaProjection projectionGrant, int stopReason,
+ boolean forStart) {
+ if (projectionGrant == null || projectionGrant.packageName == null) {
+ return true;
+ }
+ boolean disableScreenShareProtections = Settings.Global.getInt(mContentResolver,
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+ if (disableScreenShareProtections) {
+ Slog.v(TAG, "Continuing MediaProjection as screenshare protections are disabled.");
+ return true;
+ }
+
+ if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT, projectionGrant.packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ Slog.v(TAG,
+ "Continuing MediaProjection for package with RECORD_SENSITIVE_CONTENT "
+ + "permission");
+ return true;
+ }
+ if (AppOpsManager.MODE_ALLOWED == mAppOpsManager.noteOpNoThrow(
+ AppOpsManager.OP_PROJECT_MEDIA, projectionGrant.uid,
+ projectionGrant.packageName, /* attributionTag= */ null, "recording lockscreen")) {
+ // Some tools use media projection by granting the OP_PROJECT_MEDIA app
+ // op via a shell command.
+ Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp ");
+ return true;
+ }
+ if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ projectionGrant.userHandle).contains(projectionGrant.packageName)) {
+ Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role.");
+ return true;
+ }
+ if (SystemConfig.getInstance().getBugreportWhitelistedPackages().contains(
+ projectionGrant.packageName)) {
+ Slog.v(TAG, "Continuing MediaProjection for package allowlisted for bugreporting.");
+ return true;
+ }
+ if (!forStart && projectionGrant.getVirtualDisplayId() == Display.INVALID_DISPLAY) {
+ Slog.v(TAG, "Continuing MediaProjection as current projection has no VirtualDisplay.");
+ return true;
+ }
+
+ if (stopReason == STOP_REASON_CALL_END
+ && projectionGrant.getCreateTimeMillis() < mLastCallStartTimeMillis) {
+ Slog.v(TAG,
+ "Continuing MediaProjection as (phone) call started after MediaProjection was"
+ + " created.");
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return {@code true} if a MediaProjection session is currently in a restricted state.
+ */
+ public boolean isStartForbidden(
+ MediaProjectionManagerService.MediaProjection projectionGrant) {
+ if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+ return false;
+ }
+
+ if (!mKeyguardManager.isKeyguardLocked()) {
+ return false;
+ }
+
+ if (isExempt(projectionGrant, STOP_REASON_UNKNOWN, true)) {
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+ if (!isKeyguardLocked) return;
+ if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+ return;
+ }
+ mStopReasonConsumer.accept(STOP_REASON_KEYGUARD);
+ }
+
+ @VisibleForTesting
+ void callStateChanged() {
+ if (!com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) {
+ return;
+ }
+ boolean isInCall = mTelecomManager.isInCall();
+ if (isInCall) {
+ mLastCallStartTimeMillis = SystemClock.uptimeMillis();
+ }
+ if (isInCall == mIsInCall) {
+ return;
+ }
+
+ if (mIsInCall && !isInCall) {
+ mStopReasonConsumer.accept(STOP_REASON_CALL_END);
+ }
+ mIsInCall = isInCall;
+ }
+
+ /**
+ * @return a String representation of the stop reason interrupting MediaProjection.
+ */
+ public static String stopReasonToString(int stopReason) {
+ switch (stopReason) {
+ case STOP_REASON_KEYGUARD -> {
+ return "STOP_REASON_KEYGUARD";
+ }
+ case STOP_REASON_CALL_END -> {
+ return "STOP_REASON_CALL_END";
+ }
+ }
+ return "";
+ }
+
+ private final class ProjectionTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener {
+ @Override
+ public void onCallStateChanged(int state) {
+ callStateChanged();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java b/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java
new file mode 100644
index 000000000000..04f6216694a9
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.quality;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.media.quality.MediaQualityContract.BaseParameters;
+
+public class MediaQualityDbHelper extends SQLiteOpenHelper {
+
+ private static final String TAG = "MediaQualityDbHelper";
+
+ static final int DATABASE_VERSION_1 = 1;
+ private static final String DATABASE_NAME = "media_quality.db";
+ public static final String PICTURE_QUALITY_TABLE_NAME = "picture_quality";
+ public static final String SOUND_QUALITY_TABLE_NAME = "sound_quality";
+ public static final String SETTINGS = "settings";
+
+ MediaQualityDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, getDbVersion());
+ }
+
+ private static int getDbVersion() {
+ return DATABASE_VERSION_1;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(getTableCreateStatement(PICTURE_QUALITY_TABLE_NAME));
+ db.execSQL(getTableCreateStatement(SOUND_QUALITY_TABLE_NAME));
+ }
+
+ private String getTableCreateStatement(String tableName) {
+ return
+ "CREATE TABLE " + tableName + "("
+ + BaseParameters.PARAMETER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + BaseParameters.PARAMETER_TYPE + " INTEGER,"
+ + BaseParameters.PARAMETER_NAME + " STRING,"
+ + BaseParameters.PARAMETER_PACKAGE + " STRING,"
+ + BaseParameters.PARAMETER_INPUT_ID + " STRING,"
+ + SETTINGS + " TEXT)";
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // to do
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
new file mode 100644
index 000000000000..1673b8e6a0af
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.quality;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
+import android.media.quality.IMediaQualityManager;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.MediaQualityContract.BaseParameters;
+import android.media.quality.ParamCapability;
+import android.media.quality.PictureProfile;
+import android.media.quality.PictureProfileHandle;
+import android.media.quality.SoundProfile;
+import android.media.quality.SoundProfileHandle;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * This service manage picture profile and sound profile for TV setting. Also communicates with the
+ * database to save, update the profiles.
+ */
+public class MediaQualityService extends SystemService {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "MediaQualityService";
+ private final Context mContext;
+ private final MediaQualityDbHelper mMediaQualityDbHelper;
+ private final BiMap<Long, String> mPictureProfileTempIdMap;
+ private final BiMap<Long, String> mSoundProfileTempIdMap;
+
+ public MediaQualityService(Context context) {
+ super(context);
+ mContext = context;
+ mPictureProfileTempIdMap = HashBiMap.create();
+ mSoundProfileTempIdMap = HashBiMap.create();
+ mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
+ mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
+ mMediaQualityDbHelper.setIdleConnectionTimeout(30);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
+ }
+
+ // TODO: Add additional APIs. b/373951081
+ private final class BinderService extends IMediaQualityManager.Stub {
+
+ @Override
+ public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(BaseParameters.PARAMETER_TYPE, pp.getProfileType());
+ values.put(BaseParameters.PARAMETER_NAME, pp.getName());
+ values.put(BaseParameters.PARAMETER_PACKAGE, pp.getPackageName());
+ values.put(BaseParameters.PARAMETER_INPUT_ID, pp.getInputId());
+ values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(pp.getParameters()));
+
+ // id is auto-generated by SQLite upon successful insertion of row
+ Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ null, values);
+ populateTempIdMap(mPictureProfileTempIdMap, id);
+ pp.setProfileId(mPictureProfileTempIdMap.get(id));
+ return pp;
+ }
+
+ @Override
+ public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
+ // TODO: implement
+ }
+
+ @Override
+ public void removePictureProfile(String id, UserHandle user) {
+ Long intId = mPictureProfileTempIdMap.inverse().get(id);
+ if (intId != null) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArgs = {Long.toString(intId)};
+ db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
+ selectionArgs);
+ mPictureProfileTempIdMap.remove(intId);
+ }
+ }
+
+ @Override
+ public PictureProfile getPictureProfile(int type, String name, boolean includeParams,
+ UserHandle user) {
+ String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ + BaseParameters.PARAMETER_NAME + " = ?";
+ String[] selectionArguments = {Integer.toString(type), name};
+
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ getAllMediaProfileColumns(), selection, selectionArguments)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s"
+ + " in %s. Should only ever be 0 or 1.", count, type, name,
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+ return null;
+ }
+ cursor.moveToFirst();
+ return getPictureProfileWithTempIdFromCursor(cursor);
+ }
+ }
+
+ @Override
+ public List<PictureProfile> getPictureProfilesByPackage(
+ String packageName, boolean includeParams, UserHandle user) {
+ String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {packageName};
+ return getPictureProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
+ selectionArguments);
+ }
+
+ @Override
+ public List<PictureProfile> getAvailablePictureProfiles(
+ boolean includeParams, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
+ // TODO: pass the profile ID to MediaQuality HAL when ready.
+ return false;
+ }
+
+ @Override
+ public List<String> getPictureProfilePackageNames(UserHandle user) {
+ String [] column = {BaseParameters.PARAMETER_PACKAGE};
+ List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
+ null, null);
+ return pictureProfiles.stream()
+ .map(PictureProfile::getPackageName)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(BaseParameters.PARAMETER_TYPE, sp.getProfileType());
+ values.put(BaseParameters.PARAMETER_NAME, sp.getName());
+ values.put(BaseParameters.PARAMETER_PACKAGE, sp.getPackageName());
+ values.put(BaseParameters.PARAMETER_INPUT_ID, sp.getInputId());
+ values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(sp.getParameters()));
+
+ // id is auto-generated by SQLite upon successful insertion of row
+ Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ null, values);
+ populateTempIdMap(mSoundProfileTempIdMap, id);
+ sp.setProfileId(mSoundProfileTempIdMap.get(id));
+ return sp;
+ }
+
+ @Override
+ public void updateSoundProfile(String id, SoundProfile pp, UserHandle user) {
+ // TODO: implement
+ }
+
+ @Override
+ public void removeSoundProfile(String id, UserHandle user) {
+ Long intId = mSoundProfileTempIdMap.inverse().get(id);
+ if (intId != null) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArgs = {Long.toString(intId)};
+ db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
+ selectionArgs);
+ mSoundProfileTempIdMap.remove(intId);
+ }
+ }
+
+ @Override
+ public SoundProfile getSoundProfile(int type, String id, boolean includeParams,
+ UserHandle user) {
+ String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ + BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArguments = {String.valueOf(type), id};
+
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ getAllMediaProfileColumns(), selection, selectionArguments)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s"
+ + " in %s. Should only ever be 0 or 1.", count, id,
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
+ return null;
+ }
+ cursor.moveToFirst();
+ return getSoundProfileWithTempIdFromCursor(cursor);
+ }
+ }
+
+ @Override
+ public List<SoundProfile> getSoundProfilesByPackage(
+ String packageName, boolean includeParams, UserHandle user) {
+ String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {packageName};
+ return getSoundProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
+ selectionArguments);
+ }
+
+ @Override
+ public List<SoundProfile> getAvailableSoundProfiles(
+ boolean includeParams, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
+ // TODO: pass the profile ID to MediaQuality HAL when ready.
+ return false;
+ }
+
+ @Override
+ public List<String> getSoundProfilePackageNames(UserHandle user) {
+ String [] column = {BaseParameters.PARAMETER_NAME};
+ List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column,
+ null, null);
+ return soundProfiles.stream()
+ .map(SoundProfile::getPackageName)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ private void populateTempIdMap(BiMap<Long, String> map, Long id) {
+ if (id != null && map.get(id) == null) {
+ String uuid = UUID.randomUUID().toString();
+ while (map.inverse().containsKey(uuid)) {
+ uuid = UUID.randomUUID().toString();
+ }
+ map.put(id, uuid);
+ }
+ }
+
+ private String persistableBundleToJson(PersistableBundle bundle) {
+ JSONObject json = new JSONObject();
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+ try {
+ if (value instanceof String) {
+ json.put(key, bundle.getString(key));
+ } else if (value instanceof Integer) {
+ json.put(key, bundle.getInt(key));
+ } else if (value instanceof Long) {
+ json.put(key, bundle.getLong(key));
+ } else if (value instanceof Boolean) {
+ json.put(key, bundle.getBoolean(key));
+ } else if (value instanceof Double) {
+ json.put(key, bundle.getDouble(key));
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Unable to serialize ", e);
+ }
+ }
+ return json.toString();
+ }
+
+ private PersistableBundle jsonToBundle(String jsonString) {
+ PersistableBundle bundle = new PersistableBundle();
+ if (jsonString != null) {
+ JSONObject jsonObject = null;
+ try {
+ jsonObject = new JSONObject(jsonString);
+
+ Iterator<String> keys = jsonObject.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object value = jsonObject.get(key);
+
+ if (value instanceof String) {
+ bundle.putString(key, (String) value);
+ } else if (value instanceof Integer) {
+ bundle.putInt(key, (Integer) value);
+ } else if (value instanceof Boolean) {
+ bundle.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Double) {
+ bundle.putDouble(key, (Double) value);
+ } else if (value instanceof Long) {
+ bundle.putLong(key, (Long) value);
+ }
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return bundle;
+ }
+
+ private String[] getAllMediaProfileColumns() {
+ return new String[]{
+ BaseParameters.PARAMETER_ID,
+ BaseParameters.PARAMETER_TYPE,
+ BaseParameters.PARAMETER_NAME,
+ BaseParameters.PARAMETER_INPUT_ID,
+ BaseParameters.PARAMETER_PACKAGE,
+ mMediaQualityDbHelper.SETTINGS
+ };
+ }
+
+ private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) {
+ return new PictureProfile(
+ getTempId(mPictureProfileTempIdMap, cursor),
+ getType(cursor),
+ getName(cursor),
+ getInputId(cursor),
+ getPackageName(cursor),
+ jsonToBundle(getSettingsString(cursor)),
+ PictureProfileHandle.NONE
+ );
+ }
+
+ private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) {
+ return new SoundProfile(
+ getTempId(mSoundProfileTempIdMap, cursor),
+ getType(cursor),
+ getName(cursor),
+ getInputId(cursor),
+ getPackageName(cursor),
+ jsonToBundle(getSettingsString(cursor)),
+ SoundProfileHandle.NONE
+ );
+ }
+
+ private String getTempId(BiMap<Long, String> map, Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
+ Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
+ populateTempIdMap(map, dbId);
+ return map.get(dbId);
+ }
+
+ private int getType(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
+ return colIndex != -1 ? cursor.getInt(colIndex) : 0;
+ }
+
+ private String getName(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private String getInputId(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private String getPackageName(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private String getSettingsString(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(mMediaQualityDbHelper.SETTINGS);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private Cursor getCursorAfterQuerying(String table, String[] columns, String selection,
+ String[] selectionArgs) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+ return db.query(table, columns, selection, selectionArgs,
+ /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null);
+ }
+
+ private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
+ String selection, String[] selectionArguments) {
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, columns, selection,
+ selectionArguments)
+ ) {
+ List<PictureProfile> pictureProfiles = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor));
+ }
+ return pictureProfiles;
+ }
+ }
+
+ private List<SoundProfile> getSoundProfilesBasedOnConditions(String[] columns,
+ String selection, String[] selectionArguments) {
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, columns, selection,
+ selectionArguments)
+ ) {
+ List<SoundProfile> soundProfiles = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor));
+ }
+ return soundProfiles;
+ }
+ }
+
+ @Override
+ public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
+ }
+ @Override
+ public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
+ }
+
+ @Override
+ public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+ }
+
+ @Override
+ public void setAmbientBacklightSettings(
+ AmbientBacklightSettings settings, UserHandle user) {
+ }
+
+ @Override
+ public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) {
+ }
+
+ @Override
+ public List<ParamCapability> getParamCapabilities(List<String> names, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<String> getPictureProfileAllowList(UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
+ }
+
+ @Override
+ public List<String> getSoundProfileAllowList(UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
+ }
+
+ @Override
+ public boolean isSupported(UserHandle user) {
+ return false;
+ }
+
+ @Override
+ public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
+ }
+
+ @Override
+ public boolean isAutoPictureQualityEnabled(UserHandle user) {
+ return false;
+ }
+
+ @Override
+ public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
+ }
+
+ @Override
+ public boolean isSuperResolutionEnabled(UserHandle user) {
+ return false;
+ }
+
+ @Override
+ public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
+ }
+
+ @Override
+ public boolean isAutoSoundQualityEnabled(UserHandle user) {
+ return false;
+ }
+
+ @Override
+ public boolean isAmbientBacklightEnabled(UserHandle user) {
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/quality/OWNERS b/services/core/java/com/android/server/media/quality/OWNERS
new file mode 100644
index 000000000000..e455846dd75d
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/OWNERS
@@ -0,0 +1,2 @@
+shubang@google.com
+haofanw@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/CalendarTracker.java b/services/core/java/com/android/server/notification/CalendarTracker.java
index cfcf6ebf9540..f186f4273f61 100644
--- a/services/core/java/com/android/server/notification/CalendarTracker.java
+++ b/services/core/java/com/android/server/notification/CalendarTracker.java
@@ -32,6 +32,8 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.Date;
import java.util.Objects;
@@ -296,4 +298,8 @@ public class CalendarTracker {
void onChanged();
}
+ @VisibleForTesting // (otherwise = NONE)
+ public int getUserId() {
+ return mUserContext.getUserId();
+ }
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 66e61c076030..0b40d64e3a09 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -169,16 +169,15 @@ public class ConditionProviders extends ManagedServices {
for (int i = 0; i < mSystemConditionProviders.size(); i++) {
mSystemConditionProviders.valueAt(i).onBootComplete();
}
- if (mCallback != null) {
- mCallback.onBootComplete();
- }
}
@Override
public void onUserSwitched(int user) {
super.onUserSwitched(user);
- if (mCallback != null) {
- mCallback.onUserSwitched();
+ if (android.app.Flags.modesHsum()) {
+ for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+ mSystemConditionProviders.valueAt(i).onUserSwitched(UserHandle.of(user));
+ }
}
}
@@ -515,10 +514,8 @@ public class ConditionProviders extends ManagedServices {
}
public interface Callback {
- void onBootComplete();
void onServiceAdded(ComponentName component);
void onConditionChanged(Uri id, Condition condition);
- void onUserSwitched();
}
}
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index c3ace15a8c0f..bb8ccd20f9ab 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
+import android.os.UserHandle;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
@@ -65,6 +66,11 @@ public class CountdownConditionProvider extends SystemConditionProviderService {
}
@Override
+ public void onUserSwitched(UserHandle user) {
+ // Nothing to do because countdown conditions are not tied to any user data.
+ }
+
+ @Override
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" CountdownConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
diff --git a/services/core/java/com/android/server/notification/CustomManualConditionProvider.java b/services/core/java/com/android/server/notification/CustomManualConditionProvider.java
index 9531c5e58851..52dc116c699d 100644
--- a/services/core/java/com/android/server/notification/CustomManualConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CustomManualConditionProvider.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.net.Uri;
+import android.os.UserHandle;
import android.service.notification.ZenModeConfig;
import java.io.PrintWriter;
@@ -40,6 +41,11 @@ public class CustomManualConditionProvider extends SystemConditionProviderServic
}
@Override
+ public void onUserSwitched(UserHandle user) {
+ // Nothing to do because we won't ever call notifyConditions.
+ }
+
+ @Override
public void onConnected() {
// No need to keep subscriptions because we won't ever call notifyConditions
}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 925ba1752fe2..3e96afe9bee3 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -39,9 +39,11 @@ import android.service.notification.ZenModeConfig.ConfigOrigin;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Keep;
/** Default implementation for {@link DeviceEffectsApplier}. */
-class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
+@Keep
+public class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
private static final String TAG = "DeviceEffectsApplier";
private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN =
"DefaultDeviceEffectsApplier:SuppressAmbientDisplay";
@@ -63,10 +65,10 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
@GuardedBy("mRegisterReceiverLock")
private boolean mIsScreenOffReceiverRegistered;
- private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build();
+ protected ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build();
private boolean mPendingNightMode;
- DefaultDeviceEffectsApplier(Context context) {
+ public DefaultDeviceEffectsApplier(Context context) {
mContext = context;
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
mKeyguardManager = context.getSystemService(KeyguardManager.class);
@@ -79,56 +81,69 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
@Override
public void apply(ZenDeviceEffects effects, @ConfigOrigin int origin) {
- Binder.withCleanCallingIdentity(() -> {
- if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
- != effects.shouldSuppressAmbientDisplay()) {
- try {
- traceApplyDeviceEffect("suppressAmbientDisplay",
- effects.shouldSuppressAmbientDisplay());
- mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
- effects.shouldSuppressAmbientDisplay());
- } catch (Exception e) {
- Slog.e(TAG, "Could not change AOD override", e);
- }
- }
+ Binder.withCleanCallingIdentity(
+ () -> {
+ maybeSuppressAmbientDisplay(effects.shouldSuppressAmbientDisplay());
+ maybeDisplayGrayscale(effects.shouldDisplayGrayscale());
+ maybeDimWallpaper(effects.shouldDimWallpaper());
+ maybeUseNightMode(effects.shouldUseNightMode(), origin);
+ });
- if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) {
- if (mColorDisplayManager != null) {
- try {
- traceApplyDeviceEffect("displayGrayscale",
- effects.shouldDisplayGrayscale());
- mColorDisplayManager.setSaturationLevel(
- effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
- : SATURATION_LEVEL_FULL_COLOR);
- } catch (Exception e) {
- Slog.e(TAG, "Could not change grayscale override", e);
- }
- }
+ mLastAppliedEffects = effects;
+ }
+
+ protected void maybeSuppressAmbientDisplay(boolean shouldSuppressAmbientDisplay) {
+ if (mLastAppliedEffects.shouldSuppressAmbientDisplay() != shouldSuppressAmbientDisplay) {
+ try {
+ traceApplyDeviceEffect("suppressAmbientDisplay", shouldSuppressAmbientDisplay);
+ mPowerManager.suppressAmbientDisplay(
+ SUPPRESS_AMBIENT_DISPLAY_TOKEN, shouldSuppressAmbientDisplay);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change AOD override", e);
}
+ }
+ }
- if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) {
- if (mWallpaperManager != null) {
- try {
- traceApplyDeviceEffect("dimWallpaper", effects.shouldDimWallpaper());
- mWallpaperManager.setWallpaperDimAmount(
- effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
- : WALLPAPER_DIM_AMOUNT_NORMAL);
- } catch (Exception e) {
- Slog.e(TAG, "Could not change wallpaper override", e);
- }
+ protected void maybeDisplayGrayscale(boolean shouldDisplayGrayscale) {
+ if (mLastAppliedEffects.shouldDisplayGrayscale() != shouldDisplayGrayscale) {
+ if (mColorDisplayManager != null) {
+ try {
+ traceApplyDeviceEffect("displayGrayscale", shouldDisplayGrayscale);
+ mColorDisplayManager.setSaturationLevel(
+ shouldDisplayGrayscale
+ ? SATURATION_LEVEL_GRAYSCALE
+ : SATURATION_LEVEL_FULL_COLOR);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change grayscale override", e);
}
}
+ }
+ }
- if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) {
+ protected void maybeDimWallpaper(boolean shouldDimWallpaper) {
+ if (mLastAppliedEffects.shouldDimWallpaper() != shouldDimWallpaper) {
+ if (mWallpaperManager != null) {
try {
- updateOrScheduleNightMode(effects.shouldUseNightMode(), origin);
+ traceApplyDeviceEffect("dimWallpaper", shouldDimWallpaper);
+ mWallpaperManager.setWallpaperDimAmount(
+ shouldDimWallpaper
+ ? WALLPAPER_DIM_AMOUNT_DIMMED
+ : WALLPAPER_DIM_AMOUNT_NORMAL);
} catch (Exception e) {
- Slog.e(TAG, "Could not change dark theme override", e);
+ Slog.e(TAG, "Could not change wallpaper override", e);
}
}
- });
+ }
+ }
- mLastAppliedEffects = effects;
+ protected void maybeUseNightMode(boolean shouldUseNightMode, @ConfigOrigin int origin) {
+ if (mLastAppliedEffects.shouldUseNightMode() != shouldUseNightMode) {
+ try {
+ updateOrScheduleNightMode(shouldUseNightMode, origin);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change dark theme override", e);
+ }
+ }
}
private void updateOrScheduleNightMode(boolean useNightMode, @ConfigOrigin int origin) {
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index 00dd547f6c4e..308f299a117b 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
@@ -37,6 +38,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.notification.CalendarTracker.CheckEventResult;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import com.android.server.pm.PackageManagerService;
@@ -59,12 +61,13 @@ public class EventConditionProvider extends SystemConditionProviderService {
private static final String EXTRA_TIME = "time";
private static final long CHANGE_DELAY = 2 * 1000; // coalesce chatty calendar changes
- private final Context mContext = this;
+ @VisibleForTesting Context mContext = this;
private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
private final Handler mWorker;
private final HandlerThread mThread;
+ private UserHandle mCurrentUser = UserHandle.SYSTEM;
private boolean mConnected;
private boolean mRegistered;
private boolean mBootComplete; // don't hammer the calendar provider until boot completes.
@@ -114,10 +117,31 @@ public class EventConditionProvider extends SystemConditionProviderService {
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- reloadTrackers();
+ if (android.app.Flags.modesHsum()) {
+ // Possibly the intent signals a profile added on a different user, but it
+ // doesn't matter (except for a bit of wasted work here). We will reload
+ // trackers for that user when we switch.
+ reloadTrackers(mCurrentUser);
+ } else {
+ reloadTrackers();
+ }
}
}, filter);
- reloadTrackers();
+
+ if (android.app.Flags.modesHsum()) {
+ reloadTrackers(UserHandle.SYSTEM);
+ } else {
+ reloadTrackers();
+ }
+ }
+
+ @Override
+ public void onUserSwitched(UserHandle user) {
+ if (DEBUG) Slog.d(TAG, "onUserSwitched: " + user);
+ if (mCurrentUser.getIdentifier() != user.getIdentifier()) {
+ mCurrentUser = user;
+ reloadTrackers(user);
+ }
}
@Override
@@ -157,8 +181,41 @@ public class EventConditionProvider extends SystemConditionProviderService {
}
}
+ private void reloadTrackers(UserHandle user) {
+ if (DEBUG) Slog.d(TAG, "reloadTrackers user=" + user);
+ for (int i = 0; i < mTrackers.size(); i++) {
+ mTrackers.valueAt(i).setCallback(null);
+ }
+ mTrackers.clear();
+
+ // Ensure that user is the main user and not a profile.
+ UserManager userManager = UserManager.get(mContext);
+ UserHandle possibleParent = userManager.getProfileParent(user);
+ if (possibleParent != null) {
+ Slog.wtf(TAG, "reloadTrackers should not be called with profile " + user
+ + "; continuing with parent " + possibleParent);
+ user = possibleParent;
+ }
+
+ for (UserInfo profile : userManager.getProfiles(user.getIdentifier())) {
+ final Context profileContext = getContextForUser(mContext, profile.getUserHandle());
+ if (profileContext == null) {
+ Slog.w(TAG, "Unable to create context for profile " + profile.id + " of user "
+ + user.getIdentifier());
+ continue;
+ }
+ mTrackers.put(profile.id, new CalendarTracker(mContext, profileContext));
+ }
+ evaluateSubscriptions();
+ }
+
+ @Deprecated // Remove when inlining MODES_HSUM
private void reloadTrackers() {
if (DEBUG) Slog.d(TAG, "reloadTrackers");
+ if (android.app.Flags.modesHsum()) {
+ Slog.wtf(TAG, "Shouldn't call reloadTrackers() without user in MODES_HSUM",
+ new Exception());
+ }
for (int i = 0; i < mTrackers.size(); i++) {
mTrackers.valueAt(i).setCallback(null);
}
@@ -219,12 +276,23 @@ public class EventConditionProvider extends SystemConditionProviderService {
final int userId = EventInfo.resolveUserId(event.userId);
final CalendarTracker tracker = mTrackers.get(userId);
if (tracker == null) {
- Slog.w(TAG, "No calendar tracker found for user " + userId);
+ Slog.w(TAG,
+ "No calendar tracker found for user " + userId + " and calendar = "
+ + event.calName);
conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
continue;
}
result = tracker.checkEvent(event, now);
}
+
+ if (result == null) {
+ Slog.e(TAG, "No CheckEventResult for userId=" + event.userId + ", calId="
+ + event.calendarId + ", calName=" + event.calName
+ + "; trackers count is " + mTrackers.size());
+ conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
+ continue;
+ }
+
if (result.recheckAt != 0
&& (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) {
reevaluateAt = result.recheckAt;
@@ -325,4 +393,9 @@ public class EventConditionProvider extends SystemConditionProviderService {
evaluateSubscriptionsW();
}
};
+
+ @VisibleForTesting // (otherwise = NONE)
+ public SparseArray<CalendarTracker> getTrackers() {
+ return mTrackers;
+ }
}
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 9d30c565bd96..4b41696a4390 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -25,10 +25,13 @@ import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_PUBLIC;
import static android.service.notification.Flags.notificationForceGrouping;
+import static android.service.notification.Flags.notificationRegroupOnClassification;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -48,11 +51,15 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
@@ -82,12 +89,30 @@ public class GroupHelper {
// with less than this value, they will be forced grouped
private static final int MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING = 3;
+ // Regrouping needed because the channel was updated, ie. importance changed
+ static final int REGROUP_REASON_CHANNEL_UPDATE = 0;
+ // Regrouping needed because of notification bundling
+ static final int REGROUP_REASON_BUNDLE = 1;
+ // Regrouping needed because of notification unbundling
+ static final int REGROUP_REASON_UNBUNDLE = 2;
+ // Regrouping needed because of notification unbundling + the original group summary exists
+ static final int REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP = 3;
+
+ @IntDef(prefix = { "REGROUP_REASON_" }, value = {
+ REGROUP_REASON_CHANNEL_UPDATE,
+ REGROUP_REASON_BUNDLE,
+ REGROUP_REASON_UNBUNDLE,
+ REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RegroupingReason {}
private final Callback mCallback;
private final int mAutoGroupAtCount;
private final int mAutogroupSparseGroupsAtCount;
private final Context mContext;
private final PackageManager mPackageManager;
+ private boolean mIsTestHarnessExempted;
// Only contains notifications that are not explicitly grouped by the app (aka no group or
// sort key).
@@ -174,6 +199,11 @@ public class GroupHelper {
NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections();
}
+ void setTestHarnessExempted(boolean isExempted) {
+ // Allow E2E tests to post ungrouped notifications
+ mIsTestHarnessExempted = ActivityManager.isRunningInUserTestHarness() && isExempted;
+ }
+
private String generatePackageKey(int userId, String pkg) {
return userId + "|" + pkg;
}
@@ -214,7 +244,7 @@ public class GroupHelper {
if (!sbn.isAppGroup()) {
sbnToBeAutogrouped = maybeGroupWithSections(record, autogroupSummaryExists);
} else {
- maybeUngroupWithSections(record);
+ maybeUngroupOnAppGrouped(record);
}
} else {
final StatusBarNotification sbn = record.getSbn();
@@ -524,11 +554,13 @@ public class GroupHelper {
}
/**
- * A non-app grouped notification has been added or updated
+ * A non-app-grouped notification has been added or updated
* Evaluate if:
* (a) an existing autogroup summary needs updated attributes
* (b) a new autogroup summary needs to be added with correct attributes
* (c) other non-app grouped children need to be moved to the autogroup
+ * (d) the notification has been updated from a groupable to a non-groupable section and needs
+ * to trigger a cleanup
*
* This method implements autogrouping with sections support.
*
@@ -538,11 +570,11 @@ public class GroupHelper {
boolean autogroupSummaryExists) {
final StatusBarNotification sbn = record.getSbn();
boolean sbnToBeAutogrouped = false;
-
final NotificationSectioner sectioner = getSection(record);
if (sectioner == null) {
+ maybeUngroupOnNonGroupableUpdate(record);
if (DEBUG) {
- Log.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+ Slog.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
}
return false;
}
@@ -555,7 +587,6 @@ public class GroupHelper {
if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
return false;
}
-
synchronized (mAggregatedNotifications) {
ArrayMap<String, NotificationAttributes> ungrouped =
mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
@@ -572,11 +603,11 @@ public class GroupHelper {
if (ungrouped.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
if (DEBUG) {
if (ungrouped.size() >= mAutoGroupAtCount) {
- Log.i(TAG,
+ Slog.i(TAG,
"Found >=" + mAutoGroupAtCount
+ " ungrouped notifications => force grouping");
} else {
- Log.i(TAG, "Found aggregate summary => force grouping");
+ Slog.i(TAG, "Found aggregate summary => force grouping");
}
}
@@ -613,7 +644,24 @@ public class GroupHelper {
}
/**
- * A notification was added that's app grouped.
+ * A notification was added that was previously part of a valid section and needs to trigger
+ * GH state cleanup.
+ */
+ private void maybeUngroupOnNonGroupableUpdate(NotificationRecord record) {
+ maybeUngroupWithSections(record, getPreviousValidSectionKey(record));
+ }
+
+ /**
+ * A notification was added that is app-grouped.
+ */
+ private void maybeUngroupOnAppGrouped(NotificationRecord record) {
+ maybeUngroupWithSections(record, getSectionGroupKeyWithFallback(record));
+ }
+
+ /**
+ * Called when a notification is posted and is either app-grouped or was previously part of
+ * a valid section and needs to trigger GH state cleanup.
+ *
* Evaluate whether:
* (a) an existing autogroup summary needs updated attributes
* (b) if we need to remove our autogroup overlay for this notification
@@ -623,13 +671,20 @@ public class GroupHelper {
*
* And updates the internal state of un-app-grouped notifications and their flags.
*/
- private void maybeUngroupWithSections(NotificationRecord record) {
+ private void maybeUngroupWithSections(NotificationRecord record,
+ @Nullable FullyQualifiedGroupKey fullAggregateGroupKey) {
+ if (fullAggregateGroupKey == null) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping maybeUngroupWithSections for " + record
+ + " no valid section found.");
+ }
+ return;
+ }
+
final StatusBarNotification sbn = record.getSbn();
final String pkgName = sbn.getPackageName();
final int userId = record.getUserId();
- final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
- pkgName, getSection(record));
-
synchronized (mAggregatedNotifications) {
// if this notification still exists and has an autogroup overlay, but is now
// grouped by the app, clear the overlay
@@ -646,21 +701,22 @@ public class GroupHelper {
mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
if (DEBUG) {
- Log.i(TAG, "maybeUngroup removeAutoGroup: " + record);
+ Slog.i(TAG, "maybeUngroup removeAutoGroup: " + record);
}
mCallback.removeAutoGroup(sbn.getKey());
if (aggregatedNotificationsAttrs.isEmpty()) {
if (DEBUG) {
- Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+ Slog.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
}
mCallback.removeAutoGroupSummary(userId, pkgName,
fullAggregateGroupKey.toString());
mAggregatedNotifications.remove(fullAggregateGroupKey);
} else {
if (DEBUG) {
- Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+ Slog.i(TAG,
+ "Aggregate group not empty, updating: " + fullAggregateGroupKey);
}
updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
}
@@ -696,6 +752,10 @@ public class GroupHelper {
return;
}
+ if (mIsTestHarnessExempted) {
+ return;
+ }
+
final NotificationSectioner sectioner = getSection(record);
if (sectioner == null) {
if (DEBUG) {
@@ -722,37 +782,23 @@ public class GroupHelper {
Log.i(TAG, "isGroupChildWithoutSummary OR isGroupSummaryWithoutChild"
+ record);
}
+ addToUngroupedAndMaybeAggregate(record, fullAggregateGroupKey, sectioner);
+ return;
+ }
- ArrayMap<String, NotificationAttributes> ungrouped =
- mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey,
- new ArrayMap<>());
- ungrouped.put(record.getKey(), new NotificationAttributes(
- record.getFlags(),
- record.getNotification().getSmallIcon(),
- record.getNotification().color,
- record.getNotification().visibility,
- record.getNotification().getGroupAlertBehavior(),
- record.getChannel().getId()));
- mUngroupedAbuseNotifications.put(fullAggregateGroupKey, ungrouped);
- // Create/update summary and group if >= mAutoGroupAtCount notifications
- // or if aggregate group exists
- boolean hasSummary = !mAggregatedNotifications.getOrDefault(fullAggregateGroupKey,
- new ArrayMap<>()).isEmpty();
- if (ungrouped.size() >= mAutoGroupAtCount || hasSummary) {
+ // Check if summary & child notifications are not part of the same section/bundle
+ // Needs a check here if notification was bundled while enqueued
+ if (notificationRegroupOnClassification()
+ && android.service.notification.Flags.notificationClassification()) {
+ if (isGroupChildBundled(record, summaryByGroupKey)) {
if (DEBUG) {
- if (ungrouped.size() >= mAutoGroupAtCount) {
- Log.i(TAG,
- "Found >=" + mAutoGroupAtCount
- + " ungrouped notifications => force grouping");
- } else {
- Log.i(TAG, "Found aggregate summary => force grouping");
- }
+ Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record);
}
- aggregateUngroupedNotifications(fullAggregateGroupKey, sbn.getKey(),
- ungrouped, hasSummary, sectioner.mSummaryId);
+ moveNotificationsToNewSection(record.getUserId(), pkgName,
+ List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)),
+ REGROUP_REASON_BUNDLE);
+ return;
}
-
- return;
}
// scenario 3: sparse/singleton groups
@@ -767,6 +813,59 @@ public class GroupHelper {
}
}
+ @GuardedBy("mAggregatedNotifications")
+ private void addToUngroupedAndMaybeAggregate(NotificationRecord record,
+ FullyQualifiedGroupKey fullAggregateGroupKey, NotificationSectioner sectioner) {
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey,
+ new ArrayMap<>());
+ ungrouped.put(record.getKey(), new NotificationAttributes(
+ record.getFlags(),
+ record.getNotification().getSmallIcon(),
+ record.getNotification().color,
+ record.getNotification().visibility,
+ record.getNotification().getGroupAlertBehavior(),
+ record.getChannel().getId()));
+ mUngroupedAbuseNotifications.put(fullAggregateGroupKey, ungrouped);
+ // Create/update summary and group if >= mAutoGroupAtCount notifications
+ // or if aggregate group exists
+ boolean hasSummary = !mAggregatedNotifications.getOrDefault(fullAggregateGroupKey,
+ new ArrayMap<>()).isEmpty();
+ if (ungrouped.size() >= mAutoGroupAtCount || hasSummary) {
+ if (DEBUG) {
+ if (ungrouped.size() >= mAutoGroupAtCount) {
+ Slog.i(TAG, "Found >=" + mAutoGroupAtCount
+ + " ungrouped notifications => force grouping");
+ } else {
+ Slog.i(TAG, "Found aggregate summary => force grouping");
+ }
+ }
+ aggregateUngroupedNotifications(fullAggregateGroupKey, record.getKey(),
+ ungrouped, hasSummary, sectioner.mSummaryId);
+ }
+ }
+
+ private static boolean isGroupChildBundled(final NotificationRecord record,
+ final Map<String, NotificationRecord> summaryByGroupKey) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String groupKey = record.getSbn().getGroupKey();
+
+ if (!sbn.isAppGroup()) {
+ return false;
+ }
+
+ if (record.getNotification().isGroupSummary()) {
+ return false;
+ }
+
+ final NotificationRecord summary = summaryByGroupKey.get(groupKey);
+ if (summary == null) {
+ return false;
+ }
+
+ return NotificationChannel.SYSTEM_RESERVED_IDS.contains(record.getChannel().getId());
+ }
+
/**
* Called when a notification is removed, so that this helper can adjust the aggregate groups:
* - Removes the autogroup summary of the notification's section
@@ -788,8 +887,15 @@ public class GroupHelper {
final StatusBarNotification sbn = record.getSbn();
final String pkgName = sbn.getPackageName();
final int userId = record.getUserId();
- final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
- pkgName, getSection(record));
+
+ final FullyQualifiedGroupKey fullAggregateGroupKey = getSectionGroupKeyWithFallback(record);
+ if (fullAggregateGroupKey == null) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping autogroup cleanup for " + record + " no valid section found.");
+ }
+ return;
+ }
synchronized (mAggregatedNotifications) {
ArrayMap<String, NotificationAttributes> ungrouped =
@@ -807,14 +913,15 @@ public class GroupHelper {
if (aggregatedNotificationsAttrs.isEmpty()) {
if (DEBUG) {
- Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+ Slog.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
}
mCallback.removeAutoGroupSummary(userId, pkgName,
fullAggregateGroupKey.toString());
mAggregatedNotifications.remove(fullAggregateGroupKey);
} else {
if (DEBUG) {
- Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+ Slog.i(TAG,
+ "Aggregate group not empty, updating: " + fullAggregateGroupKey);
}
updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
}
@@ -828,12 +935,126 @@ public class GroupHelper {
}
}
+ /**
+ * Get the section key for a notification. If the section is invalid, ie. notification is not
+ * auto-groupable, then return the previous valid section, if any.
+ * @param record the notification
+ * @return a section group key, null if not found
+ */
+ @Nullable
+ private FullyQualifiedGroupKey getSectionGroupKeyWithFallback(final NotificationRecord record) {
+ final NotificationSectioner sectioner = getSection(record);
+ if (sectioner != null) {
+ return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(),
+ sectioner);
+ } else {
+ return getPreviousValidSectionKey(record);
+ }
+ }
+
+ /**
+ * Get the previous valid section key of a notification that may have been updated to an invalid
+ * section. This is needed in case a notification is updated as an ungroupable (invalid section)
+ * => auto-groups need to be updated/GH state cleanup.
+ * @param record the notification
+ * @return a section group key or null if not found
+ */
+ @Nullable
+ private FullyQualifiedGroupKey getPreviousValidSectionKey(final NotificationRecord record) {
+ synchronized (mAggregatedNotifications) {
+ final String recordKey = record.getKey();
+ // Search in ungrouped
+ for (Entry<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+ ungroupedSection : mUngroupedAbuseNotifications.entrySet()) {
+ if (ungroupedSection.getValue().containsKey(recordKey)) {
+ return ungroupedSection.getKey();
+ }
+ }
+ // Search in aggregated
+ for (Entry<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+ aggregatedSection : mAggregatedNotifications.entrySet()) {
+ if (aggregatedSection.getValue().containsKey(recordKey)) {
+ return aggregatedSection.getKey();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called when a child notification is removed, after some delay, so that this helper can
+ * trigger a forced grouping if the group has become sparse/singleton
+ * or only the summary is left.
+ *
+ * see also {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ *
+ * @param summaryRecord the group summary of the notification that was removed
+ * @param notificationList the full notification list from NotificationManagerService
+ * @param summaryByGroupKey the map of group summaries from NotificationManagerService
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ protected void onGroupedNotificationRemovedWithDelay(final NotificationRecord summaryRecord,
+ final List<NotificationRecord> notificationList,
+ final Map<String, NotificationRecord> summaryByGroupKey) {
+ final StatusBarNotification sbn = summaryRecord.getSbn();
+ if (!sbn.isAppGroup()) {
+ return;
+ }
+
+ if (summaryRecord.isCanceled) {
+ return;
+ }
+
+ if (mIsTestHarnessExempted) {
+ return;
+ }
+
+ final NotificationSectioner sectioner = getSection(summaryRecord);
+ if (sectioner == null) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping autogrouping for " + summaryRecord + " no valid section found.");
+ }
+ return;
+ }
+
+ final String pkgName = sbn.getPackageName();
+ final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
+ summaryRecord.getUserId(), pkgName, sectioner);
+
+ // This notification is already aggregated
+ if (summaryRecord.getGroupKey().equals(fullAggregateGroupKey.toString())) {
+ return;
+ }
+
+ synchronized (mAggregatedNotifications) {
+ if (isGroupSummaryWithoutChildren(summaryRecord, notificationList)) {
+ if (DEBUG) {
+ Slog.i(TAG, "isGroupSummaryWithoutChild " + summaryRecord);
+ }
+ addToUngroupedAndMaybeAggregate(summaryRecord, fullAggregateGroupKey, sectioner);
+ return;
+ }
+
+ // Check if notification removal turned this group into a sparse/singleton group
+ if (Flags.notificationForceGroupSingletons()) {
+ try {
+ groupSparseGroups(summaryRecord, notificationList, summaryByGroupKey, sectioner,
+ fullAggregateGroupKey);
+ } catch (Throwable e) {
+ Slog.wtf(TAG, "Failed to group sparse groups", e);
+ }
+ }
+ }
+ }
+
private record NotificationMoveOp(NotificationRecord record, FullyQualifiedGroupKey oldGroup,
FullyQualifiedGroupKey newGroup) { }
/**
- * Called when a notification channel is updated, so that this helper can adjust
- * the aggregate groups by moving children if their section has changed.
+ * Called when a notification channel is updated (channel attributes have changed),
+ * so that this helper can adjust the aggregate groups by moving children
+ * if their section has changed.
* see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
* @param userId the userId of the channel
* @param pkgName the channel's package
@@ -853,25 +1074,102 @@ public class GroupHelper {
}
}
- // The list of notification operations required after the channel update
- final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ regroupNotifications(userId, pkgName, notificationsToCheck,
+ REGROUP_REASON_CHANNEL_UPDATE);
+ }
+ }
- // Check any already auto-grouped notifications that may need to be re-grouped
- // after the channel update
- notificationsToMove.addAll(
- getAutogroupedNotificationsMoveOps(userId, pkgName,
- notificationsToCheck));
+ /**
+ * Called when an individuial notification's channel is updated (moved to a new channel),
+ * so that this helper can adjust the aggregate groups by moving children
+ * if their section has changed.
+ * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ *
+ * @param record the notification which had its channel updated
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onChannelUpdated(final NotificationRecord record) {
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+ notificationsToCheck.put(record.getKey(), record);
+ regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+ notificationsToCheck, REGROUP_REASON_BUNDLE);
+ }
+ }
- // Check any ungrouped notifications that may need to be auto-grouped
- // after the channel update
- notificationsToMove.addAll(
- getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+ /**
+ * Called when a notification that was classified (bundled) is restored to its original channel.
+ * The notification will be restored to its original group, if any/if summary still exists.
+ * Otherwise it will be moved to the appropriate section as an ungrouped notification.
+ *
+ * @param record the notification which had its channel updated
+ * @param originalSummaryExists the original group summary exists
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onNotificationUnbundled(final NotificationRecord record,
+ final boolean originalSummaryExists) {
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+ notificationsToCheck.put(record.getKey(), record);
+ regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+ notificationsToCheck,
+ originalSummaryExists ? REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP
+ : REGROUP_REASON_UNBUNDLE);
+ }
+ }
- // Batch move to new section
- if (!notificationsToMove.isEmpty()) {
- moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
+ @GuardedBy("mAggregatedNotifications")
+ private void regroupNotifications(int userId, String pkgName,
+ ArrayMap<String, NotificationRecord> notificationsToCheck,
+ @RegroupingReason int regroupingReason) {
+ // The list of notification operations required after the channel update
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+
+ // Check any already auto-grouped notifications that may need to be re-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getAutogroupedNotificationsMoveOps(userId, pkgName,
+ notificationsToCheck));
+
+ // Check any ungrouped notifications that may need to be auto-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+
+ // Handle "grouped correctly" notifications that were re-classified (bundled)
+ if (notificationRegroupOnClassification()) {
+ if (regroupingReason == REGROUP_REASON_BUNDLE) {
+ notificationsToMove.addAll(
+ getReclassifiedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
}
}
+
+ // Batch move to new section
+ if (!notificationsToMove.isEmpty()) {
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove, regroupingReason);
+ }
+ }
+
+ private List<NotificationMoveOp> getReclassifiedNotificationsMoveOps(int userId,
+ String pkgName, ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ for (NotificationRecord record : notificationsToCheck.values()) {
+ if (isChildOfValidAppGroup(record)) {
+ // Check if section changes
+ NotificationSectioner sectioner = getSection(record);
+ if (sectioner != null) {
+ FullyQualifiedGroupKey newFullAggregateGroupKey =
+ new FullyQualifiedGroupKey(userId, pkgName, sectioner);
+ if (DEBUG) {
+ Slog.v(TAG, "Regroup after classification: " + record + " to: "
+ + newFullAggregateGroupKey);
+ }
+ notificationsToMove.add(
+ new NotificationMoveOp(record, null, newFullAggregateGroupKey));
+ }
+ }
+ }
+ return notificationsToMove;
}
@GuardedBy("mAggregatedNotifications")
@@ -968,12 +1266,16 @@ public class GroupHelper {
@GuardedBy("mAggregatedNotifications")
private void moveNotificationsToNewSection(final int userId, final String pkgName,
- final List<NotificationMoveOp> notificationsToMove) {
+ final List<NotificationMoveOp> notificationsToMove, int regroupingReason) {
record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
boolean hasSummary) { }
// Bundled operations to apply to groups affected by the channel update
ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();
+ // App-provided (valid) groups of notifications that were classified (bundled).
+ // Summaries will be canceled if all child notifications have been bundled.
+ ArrayMap<String, String> originalGroupsOfBundledNotifications = new ArrayMap<>();
+
for (NotificationMoveOp moveOp: notificationsToMove) {
final NotificationRecord record = moveOp.record;
final FullyQualifiedGroupKey oldFullAggregateGroupKey = moveOp.oldGroup;
@@ -982,7 +1284,8 @@ public class GroupHelper {
if (DEBUG) {
Log.i(TAG,
"moveNotificationToNewSection: " + record + " " + newFullAggregateGroupKey
- + " from: " + oldFullAggregateGroupKey);
+ + " from: " + oldFullAggregateGroupKey + " regroupingReason: "
+ + regroupingReason);
}
// Update/remove aggregate summary for old group
@@ -999,33 +1302,47 @@ public class GroupHelper {
groupsToUpdate.put(oldFullAggregateGroupKey,
new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
}
+ } else {
+ if (notificationRegroupOnClassification()) {
+ // Null "old aggregate group" => this notification was re-classified from
+ // a valid app-provided group => maybe cancel the original summary
+ // if no children are left
+ originalGroupsOfBundledNotifications.put(record.getKey(), record.getGroupKey());
+ }
}
// Add moved notifications to the ungrouped list for new group and do grouping
// after all notifications have been handled
if (newFullAggregateGroupKey != null) {
- final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
+ if (notificationRegroupOnClassification()
+ && regroupingReason == REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP) {
+ // Just reset override group key, original summary exists
+ // => will be grouped back to its original group
+ record.setOverrideGroupKey(null);
+ } else {
+ final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
new ArrayMap<>());
- boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
- ArrayMap<String, NotificationAttributes> ungrouped =
+ boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
+ ArrayMap<String, NotificationAttributes> ungrouped =
mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
new ArrayMap<>());
- ungrouped.put(record.getKey(), new NotificationAttributes(
+ ungrouped.put(record.getKey(), new NotificationAttributes(
record.getFlags(),
record.getNotification().getSmallIcon(),
record.getNotification().color,
record.getNotification().visibility,
record.getNotification().getGroupAlertBehavior(),
record.getChannel().getId()));
- mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
+ mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
- record.setOverrideGroupKey(null);
+ record.setOverrideGroupKey(null);
- // Only add once, for triggering notification
- if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
- groupsToUpdate.put(newFullAggregateGroupKey,
- new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
+ // Only add once, for triggering notification
+ if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
+ groupsToUpdate.put(newFullAggregateGroupKey,
+ new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
+ }
}
}
}
@@ -1056,6 +1373,18 @@ public class GroupHelper {
}
}
}
+
+ if (notificationRegroupOnClassification()) {
+ // Cancel the summary if it's the last notification of the original app-provided group
+ for (String triggeringKey : originalGroupsOfBundledNotifications.keySet()) {
+ NotificationRecord canceledSummary =
+ mCallback.removeAppProvidedSummaryOnClassification(triggeringKey,
+ originalGroupsOfBundledNotifications.getOrDefault(triggeringKey, null));
+ if (canceledSummary != null) {
+ cacheCanceledSummary(canceledSummary);
+ }
+ }
+ }
}
static String getFullAggregateGroupKey(String pkgName,
@@ -1077,6 +1406,42 @@ public class GroupHelper {
return (record.mOriginalFlags & Notification.FLAG_AUTOGROUP_SUMMARY) != 0;
}
+ private boolean isNotificationAggregatedInSection(NotificationRecord record,
+ NotificationSectioner sectioner) {
+ final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
+ record.getUserId(), record.getSbn().getPackageName(), sectioner);
+ return record.getGroupKey().equals(fullAggregateGroupKey.toString());
+ }
+
+ private boolean isChildOfValidAppGroup(NotificationRecord record) {
+ final StatusBarNotification sbn = record.getSbn();
+ if (!sbn.isAppGroup()) {
+ return false;
+ }
+
+ if (!sbn.getNotification().isGroupChild()) {
+ return false;
+ }
+
+ if (record.isCanceled) {
+ return false;
+ }
+
+ final NotificationSectioner sectioner = getSection(record);
+ if (sectioner == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+ }
+ return false;
+ }
+
+ if (isNotificationAggregatedInSection(record, sectioner)) {
+ return false;
+ }
+
+ return true;
+ }
+
private static int getNumChildrenForGroup(@NonNull final String groupKey,
final List<NotificationRecord> notificationList) {
//TODO (b/349072751): track grouping state in GroupHelper -> do not use notificationList
@@ -1252,7 +1617,8 @@ public class GroupHelper {
}
}
- private ArrayMap<String, NotificationRecord> getSparseGroups(
+ @VisibleForTesting
+ protected ArrayMap<String, NotificationRecord> getSparseGroups(
final FullyQualifiedGroupKey fullAggregateGroupKey,
final List<NotificationRecord> notificationList,
final Map<String, NotificationRecord> summaryByGroupKey,
@@ -1264,8 +1630,8 @@ public class GroupHelper {
&& summary.getUserId() == fullAggregateGroupKey.userId
&& summary.getSbn().isAppGroup()
&& !summary.getGroupKey().equals(fullAggregateGroupKey.toString())) {
- int numChildren = getNumChildrenForGroup(summary.getSbn().getGroup(),
- notificationList);
+ int numChildren = getNumChildrenForGroupWithSection(summary.getSbn().getGroup(),
+ notificationList, sectioner);
if (numChildren > 0 && numChildren < MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING) {
sparseGroups.put(summary.getGroupKey(), summary);
}
@@ -1275,6 +1641,43 @@ public class GroupHelper {
return sparseGroups;
}
+ /**
+ * Get the number of children of a group if all match a certain section.
+ * Used for force grouping sparse groups, where the summary may match a section but the
+ * child notifications do not: ie. conversations
+ *
+ * @param groupKey the group key (name)
+ * @param notificationList all notifications list
+ * @param sectioner the section to match
+ * @return number of children in that group or -1 if section does not match
+ */
+ private int getNumChildrenForGroupWithSection(final String groupKey,
+ final List<NotificationRecord> notificationList,
+ final NotificationSectioner sectioner) {
+ int numChildren = 0;
+ for (NotificationRecord r : notificationList) {
+ if (!r.getNotification().isGroupSummary() && groupKey.equals(r.getSbn().getGroup())) {
+ NotificationSectioner childSection = getSection(r);
+ if (childSection == null || childSection != sectioner) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "getNumChildrenForGroupWithSection skip because invalid section: "
+ + groupKey + " r: " + r);
+ }
+ return -1;
+ } else {
+ numChildren++;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG,
+ "getNumChildrenForGroupWithSection " + groupKey + " numChild: " + numChildren);
+ }
+ return numChildren;
+ }
+
@GuardedBy("mAggregatedNotifications")
private void cacheCanceledSummary(NotificationRecord record) {
final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
@@ -1402,12 +1805,54 @@ public class GroupHelper {
}
}
+ protected void dump(PrintWriter pw, String prefix) {
+ synchronized (mAggregatedNotifications) {
+ if (!mUngroupedAbuseNotifications.isEmpty()) {
+ pw.println(prefix + "Ungrouped notifications:");
+ for (FullyQualifiedGroupKey groupKey: mUngroupedAbuseNotifications.keySet()) {
+ if (!mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>())
+ .isEmpty()) {
+ pw.println(prefix + prefix + groupKey.toString());
+ for (String notifKey : mUngroupedAbuseNotifications.get(groupKey)
+ .keySet()) {
+ pw.println(prefix + prefix + prefix + notifKey);
+ }
+ }
+ }
+ pw.println("");
+ }
+
+ if (!mAggregatedNotifications.isEmpty()) {
+ pw.println(prefix + "Autogrouped notifications:");
+ for (FullyQualifiedGroupKey groupKey: mAggregatedNotifications.keySet()) {
+ if (!mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>())
+ .isEmpty()) {
+ pw.println(prefix + prefix + groupKey.toString());
+ for (String notifKey : mAggregatedNotifications.get(groupKey).keySet()) {
+ pw.println(prefix + prefix + prefix + notifKey);
+ }
+ }
+ }
+ pw.println("");
+ }
+
+ if (!mCanceledSummaries.isEmpty()) {
+ pw.println(prefix + "Cached canceled summaries:");
+ for (CachedSummary summary: mCanceledSummaries.values()) {
+ pw.println(prefix + prefix + prefix + summary.key + " -> "
+ + summary.originalGroupKey);
+ }
+ pw.println("");
+ }
+ }
+ }
+
protected static class NotificationSectioner {
final String mName;
final int mSummaryId;
private final Predicate<NotificationRecord> mSectionChecker;
- public NotificationSectioner(String name, int summaryId,
+ private NotificationSectioner(String name, int summaryId,
Predicate<NotificationRecord> sectionChecker) {
mName = name;
mSummaryId = summaryId;
@@ -1435,6 +1880,10 @@ public class GroupHelper {
return false;
}
+ if (record.getSbn().getNotification().isMediaNotification()) {
+ return false;
+ }
+
return true;
}
}
@@ -1511,5 +1960,16 @@ public class GroupHelper {
void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
int cancelReason);
+
+ /**
+ * Cancels the group summary of a notification that was regrouped because of classification
+ * (bundling). Only cancels if the summary is the last notification of the original group.
+ * @param triggeringKey the triggering child notification key
+ * @param groupKey the original group key
+ * @return the canceled group summary or null if the summary was not canceled
+ */
+ @Nullable
+ NotificationRecord removeAppProvidedSummaryOnClassification(String triggeringKey,
+ @Nullable String groupKey);
}
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 03fc60cad8d6..b0ef80793cd7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -75,9 +75,7 @@ import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerService.DumpFilter;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import org.xmlpull.v1.XmlPullParser;
@@ -136,7 +134,6 @@ abstract public class ManagedServices {
private final UserProfiles mUserProfiles;
protected final IPackageManager mPm;
protected final UserManager mUm;
- private final UserManagerInternal mUserManagerInternal;
private final Config mConfig;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -198,7 +195,6 @@ abstract public class ManagedServices {
mConfig = getConfig();
mApprovalLevel = APPROVAL_BY_COMPONENT;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
abstract protected Config getConfig();
@@ -712,13 +708,21 @@ abstract public class ManagedServices {
}
}
readExtraAttributes(tag, parser, resolvedUserId);
- if (allowedManagedServicePackages == null || allowedManagedServicePackages.test(
- getPackageName(approved), resolvedUserId, getRequiredPermission())
- || approved.isEmpty()) {
- if (mUm.getUserInfo(resolvedUserId) != null) {
- addApprovedList(approved, resolvedUserId, isPrimary, userSetComponent);
- }
+ if (isUserChanged != null && approved.isEmpty()) {
+ // NAS
+ denyPregrantedAppUserSet(resolvedUserId, isPrimary);
mUseXml = true;
+ } else {
+ if (allowedManagedServicePackages == null
+ || allowedManagedServicePackages.test(
+ getPackageName(approved), resolvedUserId, getRequiredPermission())
+ || approved.isEmpty()) {
+ if (mUm.getUserInfo(resolvedUserId) != null) {
+ addApprovedList(approved, resolvedUserId, isPrimary,
+ userSetComponent);
+ }
+ mUseXml = true;
+ }
}
} else {
readExtraTag(tag, parser);
@@ -826,6 +830,17 @@ abstract public class ManagedServices {
}
}
+ protected void denyPregrantedAppUserSet(int userId, boolean isPrimary) {
+ synchronized (mApproved) {
+ ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ approvedByType = new ArrayMap<>();
+ mApproved.put(userId, approvedByType);
+ }
+ approvedByType.put(isPrimary, new ArraySet<>());
+ }
+ }
+
protected boolean isComponentEnabledForPackage(String pkg) {
synchronized (mMutex) {
return mEnabledServicesPackageNames.contains(pkg);
@@ -1370,14 +1385,9 @@ abstract public class ManagedServices {
@GuardedBy("mMutex")
protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind,
final IntArray activeUsers,
- SparseArray<ArraySet<ComponentName>> approvedComponentsByUser,
- boolean isVisibleBackgroundUser) {
- // When it is a visible background user in Automotive MUMD environment,
- // don't clear mEnabledServicesForCurrentProfile and mEnabledServicesPackageNames.
- if (!isVisibleBackgroundUser) {
- mEnabledServicesForCurrentProfiles.clear();
- mEnabledServicesPackageNames.clear();
- }
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) {
+ mEnabledServicesForCurrentProfiles.clear();
+ mEnabledServicesPackageNames.clear();
final int nUserIds = activeUsers.size();
for (int i = 0; i < nUserIds; ++i) {
@@ -1398,12 +1408,7 @@ abstract public class ManagedServices {
}
componentsToBind.put(userId, add);
- // When it is a visible background user in Automotive MUMD environment,
- // skip adding items to mEnabledServicesForCurrentProfile
- // and mEnabledServicesPackageNames.
- if (isVisibleBackgroundUser) {
- continue;
- }
+
mEnabledServicesForCurrentProfiles.addAll(userComponents);
for (int j = 0; j < userComponents.size(); j++) {
@@ -1451,10 +1456,7 @@ abstract public class ManagedServices {
IntArray userIds = mUserProfiles.getCurrentProfileIds();
boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
&& allowRebindForParentUser();
- boolean isVisibleBackgroundUser = false;
if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
- isVisibleBackgroundUser =
- mUserManagerInternal.isVisibleBackgroundFullUser(userToRebind);
userIds = new IntArray(1);
userIds.add(userToRebind);
}
@@ -1469,8 +1471,7 @@ abstract public class ManagedServices {
// Filter approvedComponentsByUser to collect all of the components that are allowed
// for the currently active user(s).
- populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser,
- isVisibleBackgroundUser);
+ populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser);
// For every current non-system connection, disconnect services that are no longer
// approved, or ALL services if we are force rebinding
diff --git a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
index 97bbc2338f47..2dd4f8392fdf 100644
--- a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import static android.service.notification.Adjustment.KEY_TYPE;
+import static android.service.notification.Flags.notificationForceGrouping;
+
import android.content.Context;
import android.util.Slog;
@@ -24,6 +27,7 @@ import android.util.Slog;
public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
private static final String TAG = "AdjustmentExtractor";
private static final boolean DBG = false;
+ private GroupHelper mGroupHelper;
public void initialize(Context ctx, NotificationUsageStats usageStats) {
@@ -35,8 +39,27 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
if (DBG) Slog.d(TAG, "skipping empty notification");
return null;
}
+
+ final boolean hasAdjustedClassification = record.hasAdjustment(KEY_TYPE);
record.applyAdjustments();
+ if (notificationForceGrouping()
+ && android.service.notification.Flags.notificationClassification()) {
+ // Classification adjustments trigger regrouping
+ if (mGroupHelper != null && hasAdjustedClassification) {
+ return new RankingReconsideration(record.getKey(), 0) {
+ @Override
+ public void work() {
+ }
+
+ @Override
+ public void applyChangesLocked(NotificationRecord record) {
+ mGroupHelper.onChannelUpdated(record);
+ }
+ };
+ }
+ }
+
return null;
}
@@ -49,4 +72,9 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
public void setZenHelper(ZenModeHelper helper) {
}
+
+ @Override
+ public void setGroupHelper(GroupHelper groupHelper) {
+ mGroupHelper = groupHelper;
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 90622091bcbe..0e390b69f174 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.Flags.sortSectionByTime;
+import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -42,6 +43,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -119,6 +121,37 @@ public final class NotificationAttentionHelper {
Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
);
+ // Bits 1, 2, 3, 4 are already taken by: beep|buzz|blink|cooldown
+ static final int MUTE_REASON_NOT_MUTED = 0;
+ static final int MUTE_REASON_NOT_AUDIBLE = 1 << 5;
+ static final int MUTE_REASON_SILENT_UPDATE = 1 << 6;
+ static final int MUTE_REASON_POST_SILENTLY = 1 << 7;
+ static final int MUTE_REASON_LISTENER_HINT = 1 << 8;
+ static final int MUTE_REASON_DND = 1 << 9;
+ static final int MUTE_REASON_GROUP_ALERT = 1 << 10;
+ static final int MUTE_REASON_FLAG_SILENT = 1 << 11;
+ static final int MUTE_REASON_RATE_LIMIT = 1 << 12;
+ static final int MUTE_REASON_OTHER_INSISTENT_PLAYING = 1 << 13;
+ static final int MUTE_REASON_SUPPRESSED_BUBBLE = 1 << 14;
+ static final int MUTE_REASON_COOLDOWN = 1 << 15;
+
+ @IntDef(prefix = { "MUTE_REASON_" }, value = {
+ MUTE_REASON_NOT_MUTED,
+ MUTE_REASON_NOT_AUDIBLE,
+ MUTE_REASON_SILENT_UPDATE,
+ MUTE_REASON_POST_SILENTLY,
+ MUTE_REASON_LISTENER_HINT,
+ MUTE_REASON_DND,
+ MUTE_REASON_GROUP_ALERT,
+ MUTE_REASON_FLAG_SILENT,
+ MUTE_REASON_RATE_LIMIT,
+ MUTE_REASON_OTHER_INSISTENT_PLAYING,
+ MUTE_REASON_SUPPRESSED_BUBBLE,
+ MUTE_REASON_COOLDOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MuteReason {}
+
private final Context mContext;
//This is NMS.mNotificationLock.
private final Object mLock;
@@ -397,6 +430,7 @@ public final class NotificationAttentionHelper {
boolean buzz = false;
boolean beep = false;
boolean blink = false;
+ @MuteReason int shouldMuteReason = MUTE_REASON_NOT_MUTED;
final String key = record.getKey();
@@ -404,10 +438,6 @@ public final class NotificationAttentionHelper {
Log.d(TAG, "buzzBeepBlinkLocked " + record);
}
- if (isPoliteNotificationFeatureEnabled(record)) {
- mStrategy.onNotificationPosted(record);
- }
-
// Should this notification make noise, vibe, or use the LED?
final boolean aboveThreshold =
mIsAutomotive
@@ -452,7 +482,8 @@ public final class NotificationAttentionHelper {
boolean vibrateOnly =
hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
- if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
+ shouldMuteReason = shouldMuteNotificationLocked(record, signals, hasAudibleAlert);
+ if (shouldMuteReason == MUTE_REASON_NOT_MUTED) {
if (!sentAccessibilityEvent) {
sendAccessibilityEvent(record);
sentAccessibilityEvent = true;
@@ -550,15 +581,17 @@ public final class NotificationAttentionHelper {
}
}
final int buzzBeepBlinkLoggingCode =
- (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+ (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
+ | getPoliteBit(record) | shouldMuteReason;
if (buzzBeepBlinkLoggingCode > 0) {
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype(buzzBeepBlinkLoggingCode));
EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
- getPolitenessState(record));
+ getPolitenessState(record), shouldMuteReason);
}
+
if (Flags.politeNotifications()) {
// Update last alert time
if (buzz || beep) {
@@ -603,41 +636,46 @@ public final class NotificationAttentionHelper {
mNMP.getNotificationByKey(mVibrateNotificationKey));
}
- boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) {
+ @MuteReason int shouldMuteNotificationLocked(final NotificationRecord record,
+ final Signals signals, boolean hasAudibleAlert) {
+ // Suppressed because no audible alert
+ if (!hasAudibleAlert) {
+ return MUTE_REASON_NOT_AUDIBLE;
+ }
// Suppressed because it's a silent update
final Notification notification = record.getNotification();
if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
- return true;
+ return MUTE_REASON_SILENT_UPDATE;
}
// Suppressed because a user manually unsnoozed something (or similar)
if (record.shouldPostSilently()) {
- return true;
+ return MUTE_REASON_POST_SILENTLY;
}
// muted by listener
final String disableEffects = disableNotificationEffects(record, signals.listenerHints);
if (disableEffects != null) {
ZenLog.traceDisableEffects(record, disableEffects);
- return true;
+ return MUTE_REASON_LISTENER_HINT;
}
// suppressed due to DND
if (record.isIntercepted()) {
- return true;
+ return MUTE_REASON_DND;
}
// Suppressed because another notification in its group handles alerting
if (record.getSbn().isGroup()) {
if (notification.suppressAlertingDueToGrouping()) {
- return true;
+ return MUTE_REASON_GROUP_ALERT;
}
}
// Suppressed because notification was explicitly flagged as silent
if (android.service.notification.Flags.notificationSilentFlag()) {
if (notification.isSilent()) {
- return true;
+ return MUTE_REASON_FLAG_SILENT;
}
}
@@ -645,12 +683,12 @@ public final class NotificationAttentionHelper {
final String pkg = record.getSbn().getPackageName();
if (mUsageStats.isAlertRateLimited(pkg)) {
Slog.e(TAG, "Muting recently noisy " + record.getKey());
- return true;
+ return MUTE_REASON_RATE_LIMIT;
}
// A different looping ringtone, such as an incoming call is playing
if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
- return true;
+ return MUTE_REASON_OTHER_INSISTENT_PLAYING;
}
// Suppressed since it's a non-interruptive update to a bubble-suppressed notification
@@ -659,11 +697,23 @@ public final class NotificationAttentionHelper {
if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
&& record.getNotification().getBubbleMetadata() != null) {
if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
- return true;
+ return MUTE_REASON_SUPPRESSED_BUBBLE;
}
}
- return false;
+ if (isPoliteNotificationFeatureEnabled(record)) {
+ // Notify the politeness strategy that an alerting notification is posted
+ if (!isInsistentUpdate(record)) {
+ mStrategy.onNotificationPosted(record);
+ }
+
+ // Suppress if politeness is muted and it's not an update for insistent
+ if (getPolitenessState(record) == PolitenessStrategy.POLITE_STATE_MUTED) {
+ return MUTE_REASON_COOLDOWN;
+ }
+ }
+
+ return MUTE_REASON_NOT_MUTED;
}
private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
@@ -1211,12 +1261,6 @@ public final class NotificationAttentionHelper {
mApplyPerPackage = applyPerPackage;
}
- boolean shouldIgnoreNotification(final NotificationRecord record) {
- // Ignore auto-group summaries => don't count them as app-posted notifications
- // for the cooldown budget
- return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
- }
-
/**
* Get the key that determines the grouping for the cooldown behavior.
*
@@ -1368,10 +1412,6 @@ public final class NotificationAttentionHelper {
@Override
public void onNotificationPosted(final NotificationRecord record) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
-
long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
@@ -1444,10 +1484,6 @@ public final class NotificationAttentionHelper {
@Override
void onNotificationPosted(NotificationRecord record) {
if (isAvalancheActive()) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
-
long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
@@ -1617,7 +1653,7 @@ public final class NotificationAttentionHelper {
}
// recent conversation
- if (record.isConversation()
+ if ((record.isConversation() || isConversationMessage(record))
&& record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
return true;
}
@@ -1632,6 +1668,21 @@ public final class NotificationAttentionHelper {
return false;
}
+
+ // Relaxed signals for conversations messages
+ private boolean isConversationMessage(final NotificationRecord record) {
+ if (!CATEGORY_MESSAGE.equals(record.getSbn().getNotification().category)) {
+ return false;
+ }
+ if (record.getChannel().isDemoted()) {
+ return false;
+ }
+ final ShortcutInfo shortcut = record.getShortcutInfo();
+ if (shortcut == null) {
+ return false;
+ }
+ return true;
+ }
}
//====================== Observers =============================
diff --git a/services/core/java/com/android/server/notification/NotificationBackupHelper.java b/services/core/java/com/android/server/notification/NotificationBackupHelper.java
new file mode 100644
index 000000000000..9df44a41877f
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationBackupHelper.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.notification;
+
+import static android.app.backup.NotificationLoggingConstants.KEY_NOTIFICATIONS;
+
+import android.app.INotificationManager;
+import android.app.backup.BlobBackupHelper;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+public class NotificationBackupHelper extends BlobBackupHelper {
+ static final String TAG = "NotifBackupHelper"; // must be < 23 chars
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // Current version of the blob schema
+ static final int BLOB_VERSION = 1;
+
+ private final int mUserId;
+
+ private final NotificationManagerInternal mNm;
+
+ public NotificationBackupHelper(int userId) {
+ super(BLOB_VERSION, KEY_NOTIFICATIONS);
+ mUserId = userId;
+
+ mNm = LocalServices.getService(NotificationManagerInternal.class);
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ byte[] newPayload = null;
+ if (KEY_NOTIFICATIONS.equals(key)) {
+ try {
+ if (android.app.Flags.backupRestoreLogging()) {
+ newPayload = mNm.getBackupPayload(mUserId, getLogger());
+ } else {
+ INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService("notification"));
+ newPayload = nm.getBackupPayload(mUserId);
+ }
+ } catch (Exception e) {
+ // Treat as no data
+ Slog.e(TAG, "Couldn't communicate with notification manager", e);
+ newPayload = null;
+ }
+ }
+ return newPayload;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (DEBUG) {
+ Slog.v(TAG, "Got restore of " + key);
+ }
+
+ if (KEY_NOTIFICATIONS.equals(key)) {
+ try {
+ if (android.app.Flags.backupRestoreLogging()) {
+ mNm.applyRestore(payload, mUserId, getLogger());
+ } else {
+ INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService("notification"));
+ nm.applyRestore(payload, mUserId);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Couldn't communicate with notification manager", e);
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 4b8de4e8c6f1..52ddb800fa40 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -19,10 +19,14 @@ package com.android.server.notification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.backup.BackupRestoreEventLogger;
import android.service.notification.DeviceEffectsApplier;
+import com.android.internal.annotations.Keep;
+
import java.util.Set;
+@Keep
public interface NotificationManagerInternal {
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
NotificationChannelGroup getNotificationChannelGroup(String pkg, int uid, String channelId);
@@ -43,7 +47,7 @@ public interface NotificationManagerInternal {
void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
- /** Get the number of notification channels for a given package */
+ /** Get the number of app created notification channels for a given package */
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
/** Does the specified package/uid have permission to post notifications? */
@@ -73,4 +77,9 @@ public interface NotificationManagerInternal {
* Otherwise an {@link IllegalStateException} will be thrown.
*/
void setDeviceEffectsApplier(DeviceEffectsApplier applier);
+
+ // Backup/restore interface
+ byte[] getBackupPayload(int user, BackupRestoreEventLogger logger);
+
+ void applyRestore(byte[] payload, int user, BackupRestoreEventLogger logger);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 08e9bb6c7cf5..39eea740a902 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -26,6 +26,7 @@ import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.Flags.notificationClassificationUi;
import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
@@ -49,10 +50,6 @@ import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
-import static android.app.NotificationChannel.NEWS_ID;
-import static android.app.NotificationChannel.PROMOTIONS_ID;
-import static android.app.NotificationChannel.RECS_ID;
-import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
@@ -86,6 +83,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA
import static android.app.NotificationManager.zenModeFromInterruptionFilter;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -108,9 +107,8 @@ import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
-import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
-import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.notificationClassification;
import static android.service.notification.Flags.notificationForceGrouping;
@@ -214,8 +212,10 @@ import android.app.RemoteServiceException.BadForegroundServiceNotificationExcept
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.StatsManager;
import android.app.UriGrantsManager;
+import android.app.ZenBypassingApp;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -240,7 +240,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.ModuleInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -462,7 +461,7 @@ public class NotificationManagerService extends SystemService {
static final int INVALID_UID = -1;
static final String ROOT_PKG = "root";
- static final String[] ALLOWED_ADJUSTMENTS = new String[] {
+ static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
Adjustment.KEY_PEOPLE,
Adjustment.KEY_SNOOZE_CRITERIA,
Adjustment.KEY_USER_SENTIMENT,
@@ -476,6 +475,10 @@ public class NotificationManagerService extends SystemService {
Adjustment.KEY_TYPE
};
+ static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] {
+ TYPE_PROMOTION
+ };
+
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
RoleManager.ROLE_DIALER,
RoleManager.ROLE_EMERGENCY
@@ -519,6 +522,7 @@ public class NotificationManagerService extends SystemService {
private static final long DELAY_FORCE_REGROUP_TIME = 3000;
+
private static final String ACTION_NOTIFICATION_TIMEOUT =
NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
private static final int REQUEST_CODE_TIMEOUT = 1;
@@ -1101,7 +1105,8 @@ public class NotificationManagerService extends SystemService {
return false;
}
- void readPolicyXml(InputStream stream, boolean forRestore, int userId)
+ void readPolicyXml(InputStream stream, boolean forRestore, int userId,
+ @Nullable BackupRestoreEventLogger logger)
throws XmlPullParserException, NumberFormatException, IOException {
final TypedXmlPullParser parser;
if (forRestore) {
@@ -1117,7 +1122,27 @@ public class NotificationManagerService extends SystemService {
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
- mZenModeHelper.readXml(parser, forRestore, userId);
+ int successfulReads = 0;
+ int unsuccessfulReads = 0;
+ try {
+ boolean loadedCorrectly =
+ mZenModeHelper.readXml(parser, forRestore, userId, logger);
+ if (loadedCorrectly)
+ successfulReads++;
+ else
+ unsuccessfulReads++;
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to read config", e);
+ unsuccessfulReads++;
+ }
+ if (logger != null) {
+ logger.logItemsRestored(DATA_TYPE_ZEN_CONFIG, successfulReads);
+ if (unsuccessfulReads > 0) {
+ logger.logItemsRestoreFailed(
+ DATA_TYPE_ZEN_CONFIG, unsuccessfulReads, ERROR_XML_PARSING);
+ }
+ }
+
} else if (PreferencesHelper.TAG_RANKING.equals(parser.getName())){
mPreferencesHelper.readXml(parser, forRestore, userId);
}
@@ -1188,7 +1213,7 @@ public class NotificationManagerService extends SystemService {
InputStream infile = null;
try {
infile = mPolicyFile.openRead();
- readPolicyXml(infile, false /*forRestore*/, USER_ALL);
+ readPolicyXml(infile, false /*forRestore*/, USER_ALL, null);
// We re-load the default dnd packages to allow the newly added and denined.
final boolean isWatch = mPackageManagerClient.hasSystemFeature(
@@ -1238,7 +1263,7 @@ public class NotificationManagerService extends SystemService {
}
try {
- writePolicyXml(stream, false /*forBackup*/, USER_ALL);
+ writePolicyXml(stream, false /*forBackup*/, USER_ALL, null);
mPolicyFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Failed to save policy file, restoring backup", e);
@@ -1249,8 +1274,8 @@ public class NotificationManagerService extends SystemService {
}
}
- private void writePolicyXml(OutputStream stream, boolean forBackup, int userId)
- throws IOException {
+ void writePolicyXml(OutputStream stream, boolean forBackup, int userId,
+ BackupRestoreEventLogger logger) throws IOException {
final TypedXmlSerializer out;
if (forBackup) {
out = Xml.newFastSerializer();
@@ -1261,7 +1286,7 @@ public class NotificationManagerService extends SystemService {
out.startDocument(null, true);
out.startTag(null, TAG_NOTIFICATION_POLICY);
out.attributeInt(null, ATTR_VERSION, DB_VERSION);
- mZenModeHelper.writeXml(out, forBackup, null, userId);
+ mZenModeHelper.writeXml(out, forBackup, null, userId, logger);
mPreferencesHelper.writeXml(out, forBackup, userId);
mListeners.writeXml(out, forBackup, userId);
mAssistants.writeXml(out, forBackup, userId);
@@ -1909,6 +1934,12 @@ public class NotificationManagerService extends SystemService {
hasSensitiveContent, lifespanMs);
}
+ protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting,
+ int classification, int lifespanMs) {
+ FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_CHANNEL_CLASSIFICATION,
+ hasPosted, isAlerting, classification, lifespanMs);
+ }
+
protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -2563,6 +2594,7 @@ public class NotificationManagerService extends SystemService {
intent.setPackage(pkg);
intent.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, id);
intent.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS, status);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
getContext().sendBroadcastAsUser(intent, UserHandle.of(userId));
});
}
@@ -2579,10 +2611,11 @@ public class NotificationManagerService extends SystemService {
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
+ mUgmInternal,
mShowReviewPermissionsNotification,
Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
- mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
+ mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat, groupHelper);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
@@ -2977,9 +3010,24 @@ public class NotificationManagerService extends SystemService {
groupKey, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
}
}
+
+ @Override
+ @Nullable
+ public NotificationRecord removeAppProvidedSummaryOnClassification(String triggeringKey,
+ @Nullable String oldGroupKey) {
+ synchronized (mNotificationLock) {
+ return removeAppProvidedSummaryOnClassificationLocked(triggeringKey,
+ oldGroupKey);
+ }
+ }
});
}
+ //Enables tests running in TH mode to be exempted from forced grouping of notifications
+ void setTestHarnessExempted(boolean isExempted) {
+ mGroupHelper.setTestHarnessExempted(isExempted);
+ }
+
private void sendRegisteredOnlyBroadcast(String action) {
sendRegisteredOnlyBroadcast(new Intent(action));
}
@@ -3043,7 +3091,7 @@ public class NotificationManagerService extends SystemService {
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
- if (android.app.Flags.modesApi()) {
+ if (android.app.Flags.modesApi() && !mZenModeHelper.hasDeviceEffectsApplier()) {
// Cannot be done earlier, as some services aren't ready until this point.
mZenModeHelper.setDeviceEffectsApplier(
new DefaultDeviceEffectsApplier(getContext()));
@@ -3997,7 +4045,7 @@ public class NotificationManagerService extends SystemService {
"canNotifyAsPackage for uid " + uid);
}
- return areNotificationsEnabledForPackageInt(pkg, uid);
+ return areNotificationsEnabledForPackageInt(uid);
}
/**
@@ -4116,23 +4164,117 @@ public class NotificationManagerService extends SystemService {
}
@Override
- @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
- public boolean canBePromoted(String pkg, int uid) {
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void allowAssistantAdjustment(String adjustmentType) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ mAssistants.allowAdjustmentType(adjustmentType);
+
+ handleSavePolicyFile();
+ }
+
+ @Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void disallowAssistantAdjustment(String adjustmentType) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ mAssistants.disallowAdjustmentType(adjustmentType);
+
+ handleSavePolicyFile();
+ }
+
+ @Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void setAdjustmentTypeSupportedState(INotificationListener token,
+ @Adjustment.Keys String key, boolean supported) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mAssistants.checkServiceTokenLocked(token);
+ if (key == null) {
+ return;
+ }
+ mAssistants.setAdjustmentTypeSupportedState(info, key, supported);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ handleSavePolicyFile();
+ }
+
+ @Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public @NonNull List<String> getUnsupportedAdjustmentTypes() {
+ checkCallerIsSystemOrSystemUiOrShell();
+ synchronized (mNotificationLock) {
+ return new ArrayList(mAssistants.mNasUnsupported.getOrDefault(
+ UserHandle.getUserId(Binder.getCallingUid()), new HashSet<>()));
+ }
+ }
+
+ @Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public @NonNull int[] getAllowedAdjustmentKeyTypes() {
+ checkCallerIsSystemOrSystemUiOrShell();
+ return mAssistants.getAllowedAdjustmentKeyTypes();
+ }
+
+ @Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void setAssistantAdjustmentKeyTypeState(int type, boolean enabled) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ mAssistants.setAssistantAdjustmentKeyTypeState(type, enabled);
+
+ handleSavePolicyFile();
+ }
+
+ @Override
+ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public @NonNull String[] getTypeAdjustmentDeniedPackages() {
checkCallerIsSystemOrSystemUiOrShell();
- if (!android.app.Flags.uiRichOngoing()) {
+ return mAssistants.getTypeAdjustmentDeniedPackages();
+ }
+
+ @Override
+ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ mAssistants.setTypeAdjustmentForPackageState(pkg, enabled);
+
+ handleSavePolicyFile();
+ }
+
+ @Override
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ public boolean appCanBePromoted(String pkg, int uid) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ if (!android.app.Flags.apiRichOngoing()) {
return false;
}
return mPreferencesHelper.canBePromoted(pkg, uid);
}
@Override
- @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
- public void setCanBePromoted(String pkg, int uid, boolean promote) {
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ public boolean canBePromoted(String callingPkg) {
+ checkCallerIsSameApp(callingPkg);
+ if (!android.app.Flags.apiRichOngoing()) {
+ return false;
+ }
+ return mPreferencesHelper.canBePromoted(callingPkg, Binder.getCallingUid());
+ }
+
+
+ /**
+ * Any changes from SystemUI or Settings should be fromUser == true. Any changes the
+ * allowlist should be fromUser == false.
+ */
+ @Override
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ public void setCanBePromoted(String pkg, int uid, boolean promote, boolean fromUser) {
checkCallerIsSystemOrSystemUiOrShell();
- if (!android.app.Flags.uiRichOngoing()) {
+ if (!android.app.Flags.apiRichOngoing()) {
return;
}
- boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote);
+ boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote, fromUser);
if (changed) {
// check for pending/posted notifs from this app and update the flag
synchronized (mNotificationLock) {
@@ -4326,8 +4468,9 @@ public class NotificationManagerService extends SystemService {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
ParceledListSlice<NotificationChannel> oldChannels =
- mPreferencesHelper.getNotificationChannels(pkg, uid, true);
- final boolean hadChannel = oldChannels != null && !oldChannels.getList().isEmpty();
+ mPreferencesHelper.getNotificationChannels(pkg, uid, true, false);
+ final boolean hadNonBundleChannel =
+ oldChannels != null && !oldChannels.getList().isEmpty();
boolean needsPolicyFileChange = false;
boolean hasRequestedNotificationPermission = false;
for (int i = 0; i < channelsSize; i++) {
@@ -4344,13 +4487,18 @@ public class NotificationManagerService extends SystemService {
mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(),
false),
NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
- boolean hasChannel = hadChannel || hasRequestedNotificationPermission;
- if (!hasChannel) {
+ boolean hasNonBundleChannel =
+ hadNonBundleChannel || hasRequestedNotificationPermission;
+ if (!hasNonBundleChannel) {
ParceledListSlice<NotificationChannel> currChannels =
- mPreferencesHelper.getNotificationChannels(pkg, uid, true);
- hasChannel = currChannels != null && !currChannels.getList().isEmpty();
- }
- if (!hadChannel && hasChannel && !hasRequestedNotificationPermission
+ mPreferencesHelper.getNotificationChannels(pkg, uid, true, false);
+ hasNonBundleChannel =
+ currChannels != null && !currChannels.getList().isEmpty();
+ }
+ // show perm prompt if new non-bundle channel added and the user has not
+ // seen the prompt
+ if (!hadNonBundleChannel && hasNonBundleChannel
+ && !hasRequestedNotificationPermission
&& startingTaskId != ActivityTaskManager.INVALID_TASK_ID) {
hasRequestedNotificationPermission = true;
if (mPermissionPolicyInternal == null) {
@@ -4585,7 +4733,7 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
int uid, boolean includeDeleted) {
enforceSystemOrSystemUI("getNotificationChannelsForPackage");
- return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted);
+ return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted, true);
}
@Override
@@ -4717,7 +4865,7 @@ public class NotificationManagerService extends SystemService {
/* ignore */
}
return mPreferencesHelper.getNotificationChannels(
- targetPkg, targetUid, false /* includeDeleted */);
+ targetPkg, targetUid, false /* includeDeleted */, true);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -4734,30 +4882,20 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public List<String> getPackagesBypassingDnd(int userId,
- boolean includeConversationChannels) {
+ public ParceledListSlice<ZenBypassingApp> getPackagesBypassingDnd(int userId)
+ throws RemoteException {
checkCallerIsSystem();
- final ArraySet<String> packageNames = new ArraySet<>();
-
- List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
- for (PackageInfo pi : pkgs) {
- String pkg = pi.packageName;
- // If any NotificationChannel for this package is bypassing, the
- // package is considered bypassing.
- for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
- pi.applicationInfo.uid).getList()) {
- // Skips non-demoted conversation channels.
- if (!includeConversationChannels
- && !TextUtils.isEmpty(channel.getConversationId())
- && !channel.isDemoted()) {
- continue;
- }
- packageNames.add(pkg);
+ UserHandle user = UserHandle.of(userId);
+ ArrayList<ZenBypassingApp> bypassing =
+ mPreferencesHelper.getPackagesBypassingDnd(userId);
+ for (int i = bypassing.size() - 1; i >= 0; i--) {
+ String pkg = bypassing.get(i).getPkg();
+ if (!areNotificationsEnabledForPackage(pkg, getUidForPackageAndUser(pkg, user))) {
+ bypassing.remove(i);
}
}
-
- return new ArrayList<String>(packageNames);
+ return new ParceledListSlice<>(bypassing);
}
@Override
@@ -4846,7 +4984,7 @@ public class NotificationManagerService extends SystemService {
throw new SecurityException("Not currently an assistant");
}
- return mAssistants.getAllowedAssistantAdjustments();
+ return new ArrayList<>(mAssistants.getAllowedAssistantAdjustments());
}
/**
@@ -5612,14 +5750,16 @@ public class NotificationManagerService extends SystemService {
final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1);
if (zenMode == -1) return;
+
+ UserHandle zenUser = getCallingZenUser();
if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) {
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(
- info.component.getPackageName(), callingUid, zenMode);
+ zenUser, info.component.getPackageName(), callingUid, zenMode);
} else {
int origin = computeZenOrigin(/* fromUser= */ false);
Binder.withCleanCallingIdentity(() -> {
- mZenModeHelper.setManualZenMode(zenMode, /* conditionId= */ null, origin,
- "listener:" + info.component.flattenToShortString(),
+ mZenModeHelper.setManualZenMode(zenUser, zenMode, /* conditionId= */ null,
+ origin, "listener:" + info.component.flattenToShortString(),
/* caller= */ info.component.getPackageName(),
callingUid);
});
@@ -5674,12 +5814,13 @@ public class NotificationManagerService extends SystemService {
public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
enforceSystemOrSystemUI("INotificationManager.setZenMode");
enforceUserOriginOnlyFromSystem(fromUser, "setZenMode");
+ UserHandle zenUser = getCallingZenUser();
final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(mode, conditionId, computeZenOrigin(fromUser),
- reason, /* caller= */ null, callingUid);
+ mZenModeHelper.setManualZenMode(zenUser, mode, conditionId,
+ computeZenOrigin(fromUser), reason, /* caller= */ null, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5689,7 +5830,7 @@ public class NotificationManagerService extends SystemService {
@Override
public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException {
enforcePolicyAccess(Binder.getCallingUid(), "getZenRules");
- return mZenModeHelper.getZenRules();
+ return mZenModeHelper.getZenRules(getCallingZenUser());
}
@Override
@@ -5698,14 +5839,14 @@ public class NotificationManagerService extends SystemService {
throw new IllegalStateException("getAutomaticZenRules called with flag off!");
}
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
- return mZenModeHelper.getAutomaticZenRules();
+ return mZenModeHelper.getAutomaticZenRules(getCallingZenUser());
}
@Override
public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule");
- return mZenModeHelper.getAutomaticZenRule(id);
+ return mZenModeHelper.getAutomaticZenRule(getCallingZenUser(), id);
}
@Override
@@ -5720,6 +5861,7 @@ public class NotificationManagerService extends SystemService {
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
enforceUserOriginOnlyFromSystem(fromUser, "addAutomaticZenRule");
+ UserHandle zenUser = getCallingZenUser();
// If the calling app is the system (from any user), take the package name from the
// rule's owner rather than from the caller's package.
@@ -5730,16 +5872,18 @@ public class NotificationManagerService extends SystemService {
}
}
- return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
+ return mZenModeHelper.addAutomaticZenRule(zenUser, rulePkg, automaticZenRule,
computeZenOrigin(fromUser), "addAutomaticZenRule", Binder.getCallingUid());
}
@Override
public void setManualZenRuleDeviceEffects(ZenDeviceEffects effects) throws RemoteException {
checkCallerIsSystem();
+ UserHandle zenUser = getCallingZenUser();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, computeZenOrigin(true),
- "Update manual mode non-policy settings", Binder.getCallingUid());
+ mZenModeHelper.setManualZenRuleDeviceEffects(zenUser, effects,
+ computeZenOrigin(true), "Update manual mode non-policy settings",
+ Binder.getCallingUid());
}
@Override
@@ -5748,8 +5892,9 @@ public class NotificationManagerService extends SystemService {
validateAutomaticZenRule(id, automaticZenRule);
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
enforceUserOriginOnlyFromSystem(fromUser, "updateAutomaticZenRule");
+ UserHandle zenUser = getCallingZenUser();
- return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
+ return mZenModeHelper.updateAutomaticZenRule(zenUser, id, automaticZenRule,
computeZenOrigin(fromUser), "updateAutomaticZenRule", Binder.getCallingUid());
}
@@ -5815,8 +5960,9 @@ public class NotificationManagerService extends SystemService {
// Verify that they can modify zen rules.
enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRule");
+ UserHandle zenUser = getCallingZenUser();
- return mZenModeHelper.removeAutomaticZenRule(id, computeZenOrigin(fromUser),
+ return mZenModeHelper.removeAutomaticZenRule(zenUser, id, computeZenOrigin(fromUser),
"removeAutomaticZenRule", Binder.getCallingUid());
}
@@ -5826,9 +5972,11 @@ public class NotificationManagerService extends SystemService {
Objects.requireNonNull(packageName, "Package name is null");
enforceSystemOrSystemUI("removeAutomaticZenRules");
enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRules");
+ UserHandle zenUser = getCallingZenUser();
- return mZenModeHelper.removeAutomaticZenRules(packageName, computeZenOrigin(fromUser),
- packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
+ return mZenModeHelper.removeAutomaticZenRules(zenUser, packageName,
+ computeZenOrigin(fromUser), packageName + "|removeAutomaticZenRules",
+ Binder.getCallingUid());
}
@Override
@@ -5836,7 +5984,7 @@ public class NotificationManagerService extends SystemService {
Objects.requireNonNull(owner, "Owner is null");
enforceSystemOrSystemUI("getRuleInstanceCount");
- return mZenModeHelper.getCurrentInstanceCount(owner);
+ return mZenModeHelper.getCurrentInstanceCount(getCallingZenUser(), owner);
}
@Override
@@ -5844,7 +5992,7 @@ public class NotificationManagerService extends SystemService {
public int getAutomaticZenRuleState(@NonNull String id) {
Objects.requireNonNull(id, "id is null");
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRuleState");
- return mZenModeHelper.getAutomaticZenRuleState(id);
+ return mZenModeHelper.getAutomaticZenRuleState(getCallingZenUser(), id);
}
@Override
@@ -5855,9 +6003,30 @@ public class NotificationManagerService extends SystemService {
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
+ UserHandle zenUser = getCallingZenUser();
- mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
- Binder.getCallingUid());
+ mZenModeHelper.setAutomaticZenRuleState(zenUser, id, condition,
+ computeZenOrigin(fromUser), Binder.getCallingUid());
+ }
+
+ /**
+ * Returns the {@link UserHandle} corresponding to the caller that is performing a
+ * zen-related operation (such as {@link #setInterruptionFilter},
+ * {@link #addAutomaticZenRule}, {@link #setAutomaticZenRuleState}, etc). The user is
+ * {@link UserHandle#USER_CURRENT} if the caller is the system or SystemUI (assuming
+ * that all interactions in SystemUI are for the "current" user); otherwise it's the user
+ * associated to the binder call.
+ */
+ private UserHandle getCallingZenUser() {
+ if (android.app.Flags.modesMultiuser()) {
+ if (isCallerSystemOrSystemUiOrShell()) {
+ return UserHandle.CURRENT;
+ } else {
+ return Binder.getCallingUserHandle();
+ }
+ } else {
+ return UserHandle.CURRENT;
+ }
}
@ZenModeConfig.ConfigOrigin
@@ -5893,15 +6062,16 @@ public class NotificationManagerService extends SystemService {
if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
final int callingUid = Binder.getCallingUid();
enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
+ UserHandle zenUser = getCallingZenUser();
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(zenUser, pkg, callingUid, zen);
return;
}
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(zen, null, computeZenOrigin(fromUser),
+ mZenModeHelper.setManualZenMode(zenUser, zen, null, computeZenOrigin(fromUser),
/* reason= */ "setInterruptionFilter", /* caller= */ pkg,
callingUid);
} finally {
@@ -6126,7 +6296,7 @@ public class NotificationManagerService extends SystemService {
if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
- writePolicyXml(baos, true /*forBackup*/, user);
+ writePolicyXml(baos, true /*forBackup*/, user, null);
return baos.toByteArray();
} catch (IOException e) {
Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
@@ -6145,7 +6315,7 @@ public class NotificationManagerService extends SystemService {
}
final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
try {
- readPolicyXml(bais, true /*forRestore*/, user);
+ readPolicyXml(bais, true /*forRestore*/, user, null);
handleSavePolicyFile();
} catch (NumberFormatException | XmlPullParserException | IOException e) {
Slog.w(TAG, "applyRestore: error reading payload", e);
@@ -6197,12 +6367,13 @@ public class NotificationManagerService extends SystemService {
@Override
public Policy getNotificationPolicy(String pkg) {
final int callingUid = Binder.getCallingUid();
+ UserHandle zenUser = getCallingZenUser();
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
- return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(pkg);
+ return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(zenUser, pkg);
}
final long identity = Binder.clearCallingIdentity();
try {
- return mZenModeHelper.getNotificationPolicy();
+ return mZenModeHelper.getNotificationPolicy(zenUser);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -6231,7 +6402,9 @@ public class NotificationManagerService extends SystemService {
enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
@ZenModeConfig.ConfigOrigin int origin = computeZenOrigin(fromUser);
+ UserHandle zenUser = getCallingZenUser();
+ boolean isSystemCaller = isCallerSystemOrSystemUiOrShell();
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -6239,7 +6412,7 @@ public class NotificationManagerService extends SystemService {
try {
final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
0, UserHandle.getUserId(callingUid));
- Policy currPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy currPolicy = mZenModeHelper.getNotificationPolicy(zenUser);
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.P) {
int priorityCategories = policy.priorityCategories;
@@ -6268,18 +6441,41 @@ public class NotificationManagerService extends SystemService {
policy.priorityCallSenders, policy.priorityMessageSenders,
policy.suppressedVisualEffects, currPolicy.priorityConversationSenders);
}
+
int newVisualEffects = calculateSuppressedVisualEffects(
policy, currPolicy, applicationInfo.targetSdkVersion);
- policy = new Policy(policy.priorityCategories,
- policy.priorityCallSenders, policy.priorityMessageSenders,
- newVisualEffects, policy.priorityConversationSenders);
+
+ if (android.app.Flags.modesUi()) {
+ // 1. Callers should not modify STATE_CHANNELS_BYPASSING_DND, which is
+ // internally calculated and only indicates whether channels that want to bypass
+ // DND _exist_.
+ // 2. Only system callers should modify STATE_PRIORITY_CHANNELS_BLOCKED because
+ // it is @hide.
+ // 3. If the policy has been modified by the targetSdkVersion checks above then
+ // it has lost its state flags and that's fine (STATE_PRIORITY_CHANNELS_BLOCKED
+ // didn't exist until V).
+ int newState = Policy.STATE_UNSET;
+ if (isSystemCaller && policy.state != Policy.STATE_UNSET) {
+ newState = Policy.policyState(
+ currPolicy.hasPriorityChannels(),
+ policy.allowPriorityChannels());
+ }
+ policy = new Policy(policy.priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ newVisualEffects, newState, policy.priorityConversationSenders);
+ } else {
+ policy = new Policy(policy.priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ newVisualEffects, policy.priorityConversationSenders);
+ }
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(zenUser, pkg, callingUid,
+ policy);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
- mZenModeHelper.setNotificationPolicy(policy, origin, callingUid);
+ mZenModeHelper.setNotificationPolicy(zenUser, policy, origin, callingUid);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set notification policy", e);
@@ -6402,6 +6598,14 @@ public class NotificationManagerService extends SystemService {
android.Manifest.permission.INTERACT_ACROSS_USERS,
"setNotificationListenerAccessGrantedForUser for user " + userId);
}
+ if (mUmInternal.isVisibleBackgroundFullUser(userId)) {
+ // The main use case for visible background users is the Automotive multi-display
+ // configuration where a passenger can use a secondary display while the driver is
+ // using the main display. NotificationListeners is designed only for the current
+ // user and work profile. We added a condition to prevent visible background users
+ // from updating the data managed within the NotificationListeners object.
+ return;
+ }
checkNotificationListenerAccess();
if (granted && listener.flattenToString().length()
> NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
@@ -6530,6 +6734,33 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ @FlaggedApi(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+ public NotificationChannel createConversationNotificationChannelForPackageFromPrivilegedListener(
+ INotificationListener token, String pkg, UserHandle user,
+ String parentId, String conversationId) throws RemoteException {
+ Objects.requireNonNull(pkg);
+ Objects.requireNonNull(user);
+ Objects.requireNonNull(parentId);
+ Objects.requireNonNull(conversationId);
+
+ verifyPrivilegedListener(token, user, true);
+
+ int uid = getUidForPackageAndUser(pkg, user);
+ NotificationChannel conversationChannel =
+ mPreferencesHelper.getNotificationChannel(pkg, uid, parentId, false).copy();
+ String conversationChannelId = String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId);
+ conversationChannel.setId(conversationChannelId);
+ conversationChannel.setConversationId(parentId, conversationId);
+ createNotificationChannelsImpl(
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+ handleSavePolicyFile();
+
+ return mPreferencesHelper.getConversationNotificationChannel(
+ pkg, uid, parentId, conversationId, false, false).copy();
+ }
+
+ @Override
public void updateNotificationChannelGroupFromPrivilegedListener(
INotificationListener token, String pkg, UserHandle user,
NotificationChannelGroup group) throws RemoteException {
@@ -6547,7 +6778,7 @@ public class NotificationManagerService extends SystemService {
Objects.requireNonNull(pkg);
Objects.requireNonNull(user);
- verifyPrivilegedListener(token, user, false);
+ verifyPrivilegedListener(token, user, true);
final NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
pkg, getUidForPackageAndUser(pkg, user), channel.getId(), true);
@@ -6563,7 +6794,7 @@ public class NotificationManagerService extends SystemService {
verifyPrivilegedListener(token, user, true);
return mPreferencesHelper.getNotificationChannels(pkg,
- getUidForPackageAndUser(pkg, user), false /* includeDeleted */);
+ getUidForPackageAndUser(pkg, user), false /* includeDeleted */, true);
}
@Override
@@ -6657,13 +6888,7 @@ public class NotificationManagerService extends SystemService {
final Uri originalSoundUri =
(originalChannel != null) ? originalChannel.getSound() : null;
if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) {
- Binder.withCleanCallingIdentity(() -> {
- mUgmInternal.checkGrantUriPermission(sourceUid, null,
- ContentProvider.getUriWithoutUserId(soundUri),
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(soundUri,
- UserHandle.getUserId(sourceUid)));
- });
+ PermissionHelper.grantUriPermission(mUgmInternal, soundUri, sourceUid);
}
}
@@ -6804,32 +7029,34 @@ public class NotificationManagerService extends SystemService {
if (!mAssistants.isAdjustmentAllowed(potentialKey)) {
toRemove.add(potentialKey);
}
+ if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+ if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
+ toRemove.add(potentialKey);
+ } else if (notificationClassificationUi()
+ && !mAssistants.isTypeAdjustmentAllowedForPackage(
+ r.getSbn().getPackageName())) {
+ toRemove.add(potentialKey);
+ }
+ }
}
for (String removeKey : toRemove) {
adjustments.remove(removeKey);
}
- if (android.service.notification.Flags.notificationClassification()
- && adjustments.containsKey(KEY_TYPE)) {
- NotificationChannel newChannel = null;
- int type = adjustments.getInt(KEY_TYPE);
- if (TYPE_NEWS == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
- } else if (TYPE_PROMOTION == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
- } else if (TYPE_SOCIAL_MEDIA == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
- } else if (TYPE_CONTENT_RECOMMENDATION == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
- }
- if (newChannel == null) {
+ if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+ final NotificationChannel newChannel = getClassificationChannelLocked(r,
+ adjustments);
+ if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) {
adjustments.remove(KEY_TYPE);
} else {
+ // Save the app-provided type for logging.
+ int classification = adjustments.getInt(KEY_TYPE);
// swap app provided type with the real thing
adjustments.putParcelable(KEY_TYPE, newChannel);
+ // Note that this value of isAlerting does not fully indicate whether a notif
+ // would make a sound or HUN on device; it is an approximation for metrics.
+ boolean isAlerting = r.getChannel().getImportance() >= IMPORTANCE_DEFAULT;
+ logClassificationChannelAdjustmentReceived(isPosted, isAlerting, classification,
+ r.getLifespanMs(System.currentTimeMillis()));
}
}
r.addAdjustment(adjustment);
@@ -6841,6 +7068,25 @@ public class NotificationManagerService extends SystemService {
}
}
+ @GuardedBy("mNotificationLock")
+ @Nullable
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ private NotificationChannel getClassificationChannelLocked(NotificationRecord r,
+ Bundle adjustments) {
+ int type = adjustments.getInt(KEY_TYPE);
+ if (type >= TYPE_PROMOTION && type <= TYPE_CONTENT_RECOMMENDATION) {
+ NotificationChannel channel = mPreferencesHelper.getReservedChannel(
+ r.getSbn().getPackageName(), r.getUid(), type);
+ if (channel == null) {
+ channel = mPreferencesHelper.createReservedChannel(
+ r.getSbn().getPackageName(), r.getUid(), type);
+ handleSavePolicyFile();
+ }
+ return channel;
+ }
+ return null;
+ }
+
@SuppressWarnings("GuardedBy")
@GuardedBy("mNotificationLock")
void addAutogroupKeyLocked(String key, String groupName, boolean requestSort) {
@@ -6866,6 +7112,9 @@ public class NotificationManagerService extends SystemService {
if (notificationForceGrouping()) {
if (r.getSbn().isAppGroup()) {
mListeners.notifyPostedLocked(r, r);
+
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_FORCE_GROUP, r);
}
}
}
@@ -6932,6 +7181,53 @@ public class NotificationManagerService extends SystemService {
}
@GuardedBy("mNotificationLock")
+ @Nullable
+ NotificationRecord removeAppProvidedSummaryOnClassificationLocked(String triggeringKey,
+ @Nullable String oldGroupKey) {
+ NotificationRecord canceledSummary = null;
+ NotificationRecord r = mNotificationsByKey.get(triggeringKey);
+ if (r == null || oldGroupKey == null) {
+ return null;
+ }
+
+ if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) {
+ NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey);
+ // We only care about app-provided valid groups
+ if (groupSummary != null && !GroupHelper.isAggregatedGroup(groupSummary)) {
+ List<NotificationRecord> notificationsInGroup =
+ findGroupNotificationsLocked(r.getSbn().getPackageName(),
+ oldGroupKey, r.getUserId());
+ // Remove the app-provided summary if only the summary is left in the
+ // original group, or summary + triggering notification that will be
+ // regrouped
+ boolean isOnlySummaryLeft =
+ (notificationsInGroup.size() <= 1)
+ || (notificationsInGroup.size() == 2
+ && notificationsInGroup.contains(r)
+ && notificationsInGroup.contains(groupSummary));
+ if (isOnlySummaryLeft) {
+ if (DBG) {
+ Slog.i(TAG, "Removing app summary (all children bundled): "
+ + groupSummary);
+ }
+ if (convertSummaryToNotificationLocked(groupSummary.getKey())) {
+ groupSummary.isCanceled = true;
+ canceledSummary = groupSummary;
+ mSummaryByGroupKey.remove(oldGroupKey);
+ cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(),
+ groupSummary.getSbn().getPackageName(),
+ groupSummary.getSbn().getTag(),
+ groupSummary.getSbn().getId(), 0, 0, false, groupSummary.getUserId(),
+ NotificationListenerService.REASON_GROUP_OPTIMIZATION, null);
+ }
+ }
+ }
+ }
+
+ return canceledSummary;
+ }
+
+ @GuardedBy("mNotificationLock")
private boolean hasAutoGroupSummaryLocked(NotificationRecord record) {
final String autbundledGroupKey;
if (notificationForceGrouping()) {
@@ -7046,6 +7342,10 @@ public class NotificationManagerService extends SystemService {
// Clear summary flag
StatusBarNotification sbn = r.getSbn();
sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_GROUP_SUMMARY);
+
+ EventLogTags.writeNotificationSummaryConverted(key);
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_FORCE_GROUP_SUMMARY, r);
return true;
}
return false;
@@ -7307,6 +7607,11 @@ public class NotificationManagerService extends SystemService {
mTtlHelper.dump(pw, " ");
}
}
+
+ if (notificationForceGrouping()) {
+ pw.println("\n GroupHelper:");
+ mGroupHelper.dump(pw, " ");
+ }
}
}
@@ -7315,6 +7620,37 @@ public class NotificationManagerService extends SystemService {
*/
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
+ public byte[] getBackupPayload(int user, BackupRestoreEventLogger logger) {
+ checkCallerIsSystem();
+ if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writePolicyXml(baos, true /*forBackup*/, user, logger);
+ return baos.toByteArray();
+ } catch (IOException e) {
+ Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+ }
+ return null;
+ }
+
+ @Override
+ public void applyRestore(byte[] payload, int user, BackupRestoreEventLogger logger) {
+ checkCallerIsSystem();
+ if (DBG) Slog.d(TAG, "applyRestore u=" + user + " payload="
+ + (payload != null ? new String(payload, StandardCharsets.UTF_8) : null));
+ if (payload == null) {
+ Slog.w(TAG, "applyRestore: no payload to restore for user " + user);
+ return;
+ }
+ final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+ try {
+ readPolicyXml(bais, true /*forRestore*/, user, logger);
+ handleSavePolicyFile();
+ } catch (NumberFormatException | XmlPullParserException | IOException e) {
+ Slog.w(TAG, "applyRestore: error reading payload", e);
+ }
+ }
+
@Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String
channelId) {
@@ -7395,7 +7731,7 @@ public class NotificationManagerService extends SystemService {
NotificationRecord r = findNotificationLocked(pkg, null, notificationId, userId);
if (r != null) {
if (DBG) {
- final String type = (flag == FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ";
+ final String type = (flag == FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ";
Slog.d(TAG, "Remove " + type + " flag not allow. "
+ "Cancel " + type + " notification");
}
@@ -7412,7 +7748,11 @@ public class NotificationManagerService extends SystemService {
// strip flag from all enqueued notifications. listeners will be informed
// in post runnable.
StatusBarNotification sbn = r.getSbn();
- sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ if (notificationForceGrouping()) {
+ sbn.getNotification().flags = (r.getFlags() & ~flag);
+ } else {
+ sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ }
}
}
@@ -7421,7 +7761,11 @@ public class NotificationManagerService extends SystemService {
if (r != null) {
// if posted notification exists, strip its flag and tell listeners
StatusBarNotification sbn = r.getSbn();
- sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ if (notificationForceGrouping()) {
+ sbn.getNotification().flags = (r.getFlags() & ~flag);
+ } else {
+ sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ }
mRankingHelper.sort(mNotificationList);
mListeners.notifyPostedLocked(r, r);
}
@@ -7442,7 +7786,7 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
- return areNotificationsEnabledForPackageInt(pkg, uid);
+ return areNotificationsEnabledForPackageInt(uid);
}
@Override
@@ -7545,10 +7889,11 @@ public class NotificationManagerService extends SystemService {
// Make Notification silent
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
- // Repost
+ // Repost as the original app (even if it was posted by a delegate originally
+ // because the delegate may now be revoked)
enqueueNotificationInternal(r.getSbn().getPackageName(),
- r.getSbn().getOpPkg(), r.getSbn().getUid(),
- r.getSbn().getInitialPid(), r.getSbn().getTag(),
+ r.getSbn().getPackageName(), r.getSbn().getUid(),
+ MY_PID, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(),
r.getSbn().getUserId(), /* postSilently= */ true,
/* byForegroundService= */ false,
@@ -7556,8 +7901,9 @@ public class NotificationManagerService extends SystemService {
}
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
- return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted).getList()
- .size();
+ // don't show perm prompt if the only channels are bundle channels
+ return mPreferencesHelper.getNotificationChannels(
+ pkg, uid, includeDeleted, false).getList().size();
}
void cancelNotificationInternal(String pkg, String opPkg, int callingUid, int callingPid,
@@ -7769,7 +8115,7 @@ public class NotificationManagerService extends SystemService {
return false;
}
- if (android.app.Flags.uiRichOngoing()) {
+ if (android.app.Flags.apiRichOngoing()) {
// This would normally be done in fixNotification(), but we need the channel info so
// it's done a little late
if (mPreferencesHelper.canBePromoted(pkg, notificationUid)
@@ -7786,7 +8132,6 @@ public class NotificationManagerService extends SystemService {
r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));
boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
r.setImportanceFixed(isImportanceFixed);
-
if (notification.isFgsOrUij()) {
if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
|| !channel.isUserVisibleTaskShown())
@@ -7868,11 +8213,16 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Returns a channel, if exists, and restores deleted conversation channels.
+ * Returns a channel, if exists and is not a bundle channel, and restores deleted
+ * conversation channels.
*/
@Nullable
private NotificationChannel getNotificationChannelRestoreDeleted(String pkg,
int callingUid, int notificationUid, String channelId, String conversationId) {
+ if (SYSTEM_RESERVED_IDS.contains(channelId)) {
+ // apps cannot post to these channels directly, in case they post incorrect content
+ return null;
+ }
// Restore a deleted conversation channel, if exists. Otherwise use the parent channel.
NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
pkg, notificationUid, channelId, conversationId,
@@ -8415,7 +8765,7 @@ public class NotificationManagerService extends SystemService {
}
// blocked apps
- boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
+ boolean isBlocked = !areNotificationsEnabledForPackageInt(uid);
synchronized (mNotificationLock) {
isBlocked |= isRecordBlockedLocked(r);
}
@@ -8465,7 +8815,7 @@ public class NotificationManagerService extends SystemService {
}
}
- private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) {
+ private boolean areNotificationsEnabledForPackageInt(int uid) {
return mPermissionHelper.hasPermission(uid);
}
@@ -9001,7 +9351,7 @@ public class NotificationManagerService extends SystemService {
* notifying all listeners to a background thread; false otherwise.
*/
private boolean postNotification() {
- boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
+ boolean appBanned = !areNotificationsEnabledForPackageInt(uid);
boolean isCallNotification = isCallNotification(pkg, uid);
boolean posted = false;
synchronized (NotificationManagerService.this.mNotificationLock) {
@@ -9132,10 +9482,15 @@ public class NotificationManagerService extends SystemService {
// a group summary or children (complete a group)
mHandler.postDelayed(() -> {
synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPostedWithDelay(
- r, mNotificationList, mSummaryByGroupKey);
+ NotificationRecord record =
+ mNotificationsByKey.get(key);
+ if (record != null) {
+ mGroupHelper.onNotificationPostedWithDelay(
+ record, mNotificationList,
+ mSummaryByGroupKey);
+ }
}
- }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+ }, key, DELAY_FORCE_REGROUP_TIME);
}
}
@@ -9181,10 +9536,15 @@ public class NotificationManagerService extends SystemService {
if (notificationForceGrouping()) {
mHandler.postDelayed(() -> {
synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPostedWithDelay(r,
- mNotificationList, mSummaryByGroupKey);
+ NotificationRecord record =
+ mNotificationsByKey.get(key);
+ if (record != null) {
+ mGroupHelper.onNotificationPostedWithDelay(
+ record, mNotificationList,
+ mSummaryByGroupKey);
+ }
}
- }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+ }, key, DELAY_FORCE_REGROUP_TIME);
}
}
}
@@ -9419,6 +9779,28 @@ public class NotificationManagerService extends SystemService {
}
/**
+ * Check if the notification was a summary that has been auto-grouped
+ * @param r the current notification record
+ * @param old the previous notification record
+ * @return true if the notification record was a summary that was auto-grouped
+ */
+ @GuardedBy("mNotificationLock")
+ private boolean wasSummaryAutogrouped(NotificationRecord r, NotificationRecord old) {
+ boolean wasAutogrouped = false;
+ if (old != null) {
+ boolean wasSummary = (old.mOriginalFlags & FLAG_GROUP_SUMMARY) != 0;
+ boolean wasForcedGrouped = (old.getFlags() & FLAG_GROUP_SUMMARY) == 0
+ && old.getSbn().getOverrideGroupKey() != null;
+ boolean isNotAutogroupSummary = (r.getFlags() & FLAG_AUTOGROUP_SUMMARY) == 0
+ && (r.getFlags() & FLAG_GROUP_SUMMARY) != 0;
+ if ((wasSummary && wasForcedGrouped) || (wasForcedGrouped && isNotAutogroupSummary)) {
+ wasAutogrouped = true;
+ }
+ }
+ return wasAutogrouped;
+ }
+
+ /**
* Ensures that grouped notification receive their special treatment.
*
* <p>Cancels group children if the new notification causes a group to lose
@@ -9438,14 +9820,9 @@ public class NotificationManagerService extends SystemService {
}
if (notificationForceGrouping()) {
- if (old != null) {
- // If this is an update to a summary that was forced grouped => remove summary flag
- boolean wasSummary = (old.mOriginalFlags & FLAG_GROUP_SUMMARY) != 0;
- boolean wasForcedGrouped = (old.getFlags() & FLAG_GROUP_SUMMARY) == 0
- && old.getSbn().getOverrideGroupKey() != null;
- if (n.isGroupSummary() && wasSummary && wasForcedGrouped) {
- n.flags &= ~FLAG_GROUP_SUMMARY;
- }
+ // If this is an update to a summary that was forced grouped => remove summary flag
+ if (wasSummaryAutogrouped(r, old)) {
+ n.flags &= ~FLAG_GROUP_SUMMARY;
}
}
@@ -10147,12 +10524,33 @@ public class NotificationManagerService extends SystemService {
}
mListeners.notifyRemovedLocked(r, reason, r.getStats());
if (notificationForceGrouping()) {
- mHandler.removeCallbacksAndMessages(r.getKey());
+ mHandler.removeCallbacksAndEqualMessages(r.getKey());
mHandler.post(() -> {
synchronized (NotificationManagerService.this.mNotificationLock) {
mGroupHelper.onNotificationRemoved(r, mNotificationList);
}
});
+
+ // Wait 3 seconds so that the app has a chance to cancel/post
+ // a group summary or children
+ final NotificationRecord groupSummary = mSummaryByGroupKey.get(r.getGroupKey());
+ if (groupSummary != null
+ && !GroupHelper.isAggregatedGroup(groupSummary)
+ && !groupSummary.getKey().equals(canceledKey)) {
+ // We only care about app-provided valid group summaries
+ final String summaryKey = groupSummary.getKey();
+ mHandler.removeCallbacksAndEqualMessages(summaryKey);
+ mHandler.postDelayed(() -> {
+ synchronized (mNotificationLock) {
+ NotificationRecord summaryRecord = mNotificationsByKey.get(
+ summaryKey);
+ if (summaryRecord != null) {
+ mGroupHelper.onGroupedNotificationRemovedWithDelay(
+ summaryRecord, mNotificationList, mSummaryByGroupKey);
+ }
+ }
+ }, summaryKey, DELAY_FORCE_REGROUP_TIME);
+ }
} else {
mHandler.post(new Runnable() {
@Override
@@ -10733,7 +11131,7 @@ public class NotificationManagerService extends SystemService {
}
@GuardedBy("mNotificationLock")
- @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
private @NonNull List<NotificationRecord> findAppNotificationByListLocked(
ArrayList<NotificationRecord> list, String pkg, int userId) {
List<NotificationRecord> records = new ArrayList<>();
@@ -11293,12 +11691,28 @@ public class NotificationManagerService extends SystemService {
static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
private static final String ATT_TYPES = "types";
+ private static final String ATT_DENIED = "denied_adjustments";
+ private static final String ATT_ENABLED_TYPES = "enabled_key_types";
+ private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
+ private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps";
private final Object mLock = new Object();
@GuardedBy("mLock")
+ private Set<Integer> mAllowedAdjustmentKeyTypes = new ArraySet<>();
+
+ @GuardedBy("mLock")
private Set<String> mAllowedAdjustments = new ArraySet<>();
+ @GuardedBy("mLock")
+ private Set<String> mDeniedAdjustments = new ArraySet<>();
+
+ @GuardedBy("mLock")
+ private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private Set<String> mClassificationTypeDeniedPackages = new ArraySet<>();
+
protected ComponentName mDefaultFromConfig = null;
@Override
@@ -11368,9 +11782,13 @@ public class NotificationManagerService extends SystemService {
IPackageManager pm) {
super(context, lock, up, pm);
- // Add all default allowed adjustment types.
- for (int i = 0; i < ALLOWED_ADJUSTMENTS.length; i++) {
- mAllowedAdjustments.add(ALLOWED_ADJUSTMENTS[i]);
+ if (!notificationClassification()) {
+ // Add all default allowed adjustment types.
+ for (int i = 0; i < DEFAULT_ALLOWED_ADJUSTMENTS.length; i++) {
+ mAllowedAdjustments.add(DEFAULT_ALLOWED_ADJUSTMENTS[i]);
+ }
+ } else {
+ mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES));
}
}
@@ -11433,17 +11851,102 @@ public class NotificationManagerService extends SystemService {
return android.Manifest.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE;
}
- protected List<String> getAllowedAssistantAdjustments() {
+ protected Set<String> getAllowedAssistantAdjustments() {
synchronized (mLock) {
- List<String> types = new ArrayList<>();
- types.addAll(mAllowedAdjustments);
- return types;
+ if (notificationClassification()) {
+ Set<String> types = new HashSet<>(Set.of(DEFAULT_ALLOWED_ADJUSTMENTS));
+ types.removeAll(mDeniedAdjustments);
+ return types;
+ } else {
+ Set<String> types = new HashSet<>();
+ types.addAll(mAllowedAdjustments);
+ return types;
+ }
}
}
protected boolean isAdjustmentAllowed(String type) {
synchronized (mLock) {
- return mAllowedAdjustments.contains(type);
+ if (notificationClassification()) {
+ return List.of(DEFAULT_ALLOWED_ADJUSTMENTS).contains(type)
+ && !mDeniedAdjustments.contains(type);
+ } else {
+ return mAllowedAdjustments.contains(type);
+ }
+ }
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ protected @NonNull boolean isAdjustmentKeyTypeAllowed(@Adjustment.Types int type) {
+ synchronized (mLock) {
+ if (notificationClassification()) {
+ return mAllowedAdjustmentKeyTypes.contains(type);
+ }
+ }
+ return false;
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ protected @NonNull int[] getAllowedAdjustmentKeyTypes() {
+ synchronized (mLock) {
+ if (notificationClassification()) {
+ return mAllowedAdjustmentKeyTypes.stream()
+ .mapToInt(Integer::intValue).toArray();
+ }
+ }
+ return new int[]{};
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void setAssistantAdjustmentKeyTypeState(@Adjustment.Types int type,
+ boolean enabled) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (enabled) {
+ mAllowedAdjustmentKeyTypes.add(type);
+ } else {
+ mAllowedAdjustmentKeyTypes.remove(type);
+ }
+ }
+ }
+
+ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ protected @NonNull boolean isTypeAdjustmentAllowedForPackage(String pkg) {
+ synchronized (mLock) {
+ if (notificationClassificationUi()) {
+ return !mClassificationTypeDeniedPackages.contains(pkg);
+ }
+ }
+ return true;
+ }
+
+ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ protected @NonNull String[] getTypeAdjustmentDeniedPackages() {
+ synchronized (mLock) {
+ if (notificationClassificationUi()) {
+ return mClassificationTypeDeniedPackages.toArray(new String[0]);
+ }
+ }
+ return new String[]{};
+ }
+
+ /**
+ * Set whether a particular package can have its notification channels adjusted to have a
+ * different type by NotificationAssistants.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
+ if (!notificationClassificationUi()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (enabled) {
+ mClassificationTypeDeniedPackages.remove(pkg);
+ } else {
+ mClassificationTypeDeniedPackages.add(pkg);
+ }
}
}
@@ -11530,18 +12033,23 @@ public class NotificationManagerService extends SystemService {
TrimCache trimCache = new TrimCache(sbn);
final INotificationListener assistant = (INotificationListener) info.service;
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
- final StatusBarNotificationHolder sbnHolder =
- new StatusBarNotificationHolder(sbnToPost);
+ final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+
try {
- if (debug) {
- Slog.v(TAG,
- "calling onNotificationEnqueuedWithChannel " + sbnHolder);
+ if (android.app.Flags.noSbnholder()) {
+ assistant.onNotificationEnqueuedWithChannelFull(sbnToPost,
+ r.getChannel(), update);
+ } else {
+ final StatusBarNotificationHolder sbnHolder =
+ new StatusBarNotificationHolder(sbnToPost);
+
+ assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
+ update);
}
- final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
- assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
- update);
+ } catch (DeadObjectException ex) {
+ Slog.wtf(TAG, "unable to notify assistant (enqueued): " + info, ex);
} catch (RemoteException ex) {
- Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+ Slog.e(TAG, "unable to notify assistant (enqueued): " + info, ex);
}
}
}
@@ -11559,7 +12067,7 @@ public class NotificationManagerService extends SystemService {
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (assistant, unused) -> {
try {
assistant.onNotificationVisibilityChanged(key, isVisible);
} catch (RemoteException ex) {
@@ -11579,7 +12087,7 @@ public class NotificationManagerService extends SystemService {
sbn,
notificationType,
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (assistant, unused) -> {
try {
assistant.onNotificationExpansionChanged(key, isUserAction, isExpanded);
} catch (RemoteException ex) {
@@ -11596,7 +12104,7 @@ public class NotificationManagerService extends SystemService {
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (assistant, unused) -> {
try {
assistant.onNotificationDirectReply(key);
} catch (RemoteException ex) {
@@ -11614,7 +12122,7 @@ public class NotificationManagerService extends SystemService {
sbn,
notificationType,
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (assistant, unused) -> {
try {
assistant.onSuggestedReplySent(
key,
@@ -11637,7 +12145,7 @@ public class NotificationManagerService extends SystemService {
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (assistant, unused) -> {
try {
assistant.onActionClicked(
key,
@@ -11662,12 +12170,21 @@ public class NotificationManagerService extends SystemService {
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (info, sbnToPost) -> {
try {
- assistant.onNotificationSnoozedUntilContext(
- sbnHolder, snoozeCriterionId);
+ if (android.app.Flags.noSbnholder()) {
+ info.onNotificationSnoozedUntilContextFull(
+ sbnToPost, snoozeCriterionId);
+ } else {
+ final StatusBarNotificationHolder sbnHolder =
+ new StatusBarNotificationHolder(sbnToPost);
+ info.onNotificationSnoozedUntilContext(
+ sbnHolder, snoozeCriterionId);
+ }
+ } catch (DeadObjectException ex) {
+ Slog.wtf(TAG, "unable to notify assistant (snoozed): " + info, ex);
} catch (RemoteException ex) {
- Slog.e(TAG, "unable to notify assistant (snoozed): " + assistant, ex);
+ Slog.e(TAG, "unable to notify assistant (snoozed): " + info, ex);
}
});
}
@@ -11679,7 +12196,7 @@ public class NotificationManagerService extends SystemService {
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
+ (assistant, unused) -> {
try {
assistant.onNotificationClicked(key);
} catch (RemoteException ex) {
@@ -11722,7 +12239,7 @@ public class NotificationManagerService extends SystemService {
final StatusBarNotification sbn,
int notificationType,
boolean sameUserOnly,
- BiConsumer<INotificationListener, StatusBarNotificationHolder> callback) {
+ BiConsumer<INotificationListener, StatusBarNotification> callback) {
TrimCache trimCache = new TrimCache(sbn);
// There should be only one, but it's a list, so while we enforce
// singularity elsewhere, we keep it general here, to avoid surprises.
@@ -11744,9 +12261,7 @@ public class NotificationManagerService extends SystemService {
}
final INotificationListener assistant = (INotificationListener) info.service;
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
- final StatusBarNotificationHolder sbnHolder =
- new StatusBarNotificationHolder(sbnToPost);
- mHandler.post(() -> callback.accept(assistant, sbnHolder));
+ mHandler.post(() -> callback.accept(assistant, sbnToPost));
}
}
@@ -11791,6 +12306,10 @@ public class NotificationManagerService extends SystemService {
setNotificationAssistantAccessGrantedForUserInternal(
currentComponent, userId, false, userSet);
}
+ } else {
+ if (android.service.notification.Flags.notificationClassification()) {
+ mNasUnsupported.put(userId, new HashSet<>());
+ }
}
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
}
@@ -11798,6 +12317,157 @@ public class NotificationManagerService extends SystemService {
private boolean isVerboseLogEnabled() {
return Log.isLoggable("notification_assistant", Log.VERBOSE);
}
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @GuardedBy("mNotificationLock")
+ public void allowAdjustmentType(@Adjustment.Keys String key) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ mDeniedAdjustments.remove(key);
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ mHandler.post(() -> notifyCapabilitiesChanged(info));
+ }
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @GuardedBy("mNotificationLock")
+ public void disallowAdjustmentType(@Adjustment.Keys String key) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ mDeniedAdjustments.add(key);
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ mHandler.post(() -> notifyCapabilitiesChanged(info));
+ }
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @GuardedBy("mNotificationLock")
+ public void setAdjustmentTypeSupportedState(ManagedServiceInfo info,
+ @Adjustment.Keys String key, boolean supported) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ HashSet<String> disabledAdjustments =
+ mNasUnsupported.getOrDefault(info.userid, new HashSet<>());
+ if (supported) {
+ disabledAdjustments.remove(key);
+ } else {
+ disabledAdjustments.add(key);
+ }
+ mNasUnsupported.put(info.userid, disabledAdjustments);
+ handleSavePolicyFile();
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @GuardedBy("mNotificationLock")
+ public @NonNull Set<String> getUnsupportedAdjustments(@UserIdInt int userId) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return new HashSet<>();
+ }
+ return mNasUnsupported.getOrDefault(userId, new HashSet<>());
+ }
+
+ @Override
+ protected void writeExtraAttributes(TypedXmlSerializer out, @UserIdInt int approvedUserId)
+ throws IOException {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mNasUnsupported.containsKey(approvedUserId)) {
+ out.attribute(null, ATT_NAS_UNSUPPORTED,
+ TextUtils.join(",", mNasUnsupported.get(approvedUserId)));
+ }
+ }
+ }
+
+ @Override
+ protected void readExtraAttributes(String tag, TypedXmlPullParser parser,
+ @UserIdInt int approvedUserId) throws IOException {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ if (ManagedServices.TAG_MANAGED_SERVICES.equals(tag)) {
+ final String types = XmlUtils.readStringAttribute(parser, ATT_NAS_UNSUPPORTED);
+ synchronized (mLock) {
+ if (!TextUtils.isEmpty(types)) {
+ mNasUnsupported.put(approvedUserId, new HashSet(List.of(types.split(","))));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void writeExtraXmlTags(TypedXmlSerializer out) throws IOException {
+ if (!notificationClassification()) {
+ return;
+ }
+ synchronized (mLock) {
+ out.startTag(null, ATT_DENIED);
+ out.attribute(null, ATT_TYPES, TextUtils.join(",", mDeniedAdjustments));
+ out.endTag(null, ATT_DENIED);
+ out.startTag(null, ATT_ENABLED_TYPES);
+ out.attribute(null, ATT_TYPES,
+ TextUtils.join(",", mAllowedAdjustmentKeyTypes));
+ out.endTag(null, ATT_ENABLED_TYPES);
+ if (notificationClassificationUi()) {
+ out.startTag(null, ATT_TYPES_DENIED_APPS);
+ out.attribute(null, ATT_TYPES,
+ TextUtils.join(",", mClassificationTypeDeniedPackages));
+ out.endTag(null, ATT_TYPES_DENIED_APPS);
+ }
+ }
+ }
+
+ @Override
+ protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {
+ if (!notificationClassification()) {
+ return;
+ }
+ if (ATT_DENIED.equals(tag)) {
+ final String keys = XmlUtils.readStringAttribute(parser, ATT_TYPES);
+ synchronized (mLock) {
+ mDeniedAdjustments.clear();
+ if (!TextUtils.isEmpty(keys)) {
+ mDeniedAdjustments.addAll(Arrays.asList(keys.split(",")));
+ }
+ }
+ } else if (ATT_ENABLED_TYPES.equals(tag)) {
+ final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES);
+ synchronized (mLock) {
+ mAllowedAdjustmentKeyTypes.clear();
+ if (!TextUtils.isEmpty(types)) {
+ List<String> typeList = Arrays.asList(types.split(","));
+ for (String type : typeList) {
+ try {
+ mAllowedAdjustmentKeyTypes.add(Integer.parseInt(type));
+ } catch (NumberFormatException e) {
+ Slog.wtf(TAG, "Bad type specified", e);
+ }
+ }
+ }
+ }
+ } else if (notificationClassificationUi() && ATT_TYPES_DENIED_APPS.equals(tag)) {
+ final String apps = XmlUtils.readStringAttribute(parser, ATT_TYPES);
+ synchronized (mLock) {
+ mClassificationTypeDeniedPackages.clear();
+ if (!TextUtils.isEmpty(apps)) {
+ mClassificationTypeDeniedPackages.addAll(Arrays.asList(apps.split(",")));
+ }
+ }
+ }
+ }
+
+ private void notifyCapabilitiesChanged(final ManagedServiceInfo info) {
+ final INotificationListener assistant = (INotificationListener) info.service;
+ try {
+ assistant.onAllowedAdjustmentsChanged();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (capabilities): " + info, ex);
+ }
+ }
}
/**
@@ -11851,6 +12521,10 @@ public class NotificationManagerService extends SystemService {
if (record != null && (record.getSbn().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
&& !record.isCanceledAfterLifetimeExtension()) {
+ // Mark that the notification is being updated due to cancelation, so it won't
+ // be updated again if the app cancels multiple times.
+ record.setCanceledAfterLifetimeExtension(true);
+
boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND;
// Save the original Record's post silently value, so we can restore it after we send
@@ -11866,9 +12540,6 @@ public class NotificationManagerService extends SystemService {
PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(null);
tracker.addCleanupRunnable(() -> {
synchronized (mNotificationLock) {
- // Mark that the notification has been updated due to cancelation, so it won't
- // be updated again if the app cancels multiple times.
- record.setCanceledAfterLifetimeExtension(true);
// Set the post silently status to the record's previous value.
record.setPostSilently(savedPostSilentlyState);
// Remove FLAG_ONLY_ALERT_ONCE if the notification did not previously have it.
@@ -12084,6 +12755,20 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void onUserUnlocked(int user) {
+ if (mUmInternal.isVisibleBackgroundFullUser(user)) {
+ // The main use case for visible background users is the Automotive
+ // multi-display configuration where a passenger can use a secondary
+ // display while the driver is using the main display.
+ // NotificationListeners is designed only for the current user and work
+ // profile. We added a condition to prevent visible background users from
+ // updating the data managed within the NotificationListeners object.
+ return;
+ }
+ super.onUserUnlocked(user);
+ }
+
+ @Override
protected boolean allowRebindForParentUser() {
return true;
}
@@ -12409,18 +13094,31 @@ public class NotificationManagerService extends SystemService {
// Checks if this is a request to notify system UI about a notification that
// has been lifetime extended.
- // (We only need to check old for the flag, because in both cancellation and
- // update cases, old should have the flag, whereas in update cases the
- // new will NOT have the flag.)
- // If it is such a request, and this is system UI, we send the post request
- // only to System UI, and break as we don't need to continue checking other
- // Managed Services.
- if (info.isSystemUi() && old != null && old.getNotification() != null
+ // We check both old and new for the flag, to avoid catching updates
+ // (where new will not have the flag).
+ // If it is such a request, and this is the system UI listener, we send
+ // the post request. If it's any other listener, we skip it.
+ if (old != null && old.getNotification() != null
&& (old.getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
+ && sbn != null && sbn.getNotification() != null
+ && (sbn.getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
- final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
- listenerCalls.add(() -> notifyPosted(info, sbnToPost, update));
- break;
+ if (info.isSystemUi()) {
+ final NotificationRankingUpdate update =
+ makeRankingUpdateLocked(info);
+ listenerCalls.add(() -> notifyPosted(info, sbnToPost, update));
+ break;
+ } else {
+ // Skipping because this is the direct-reply "update" and we only
+ // need to send it to sysui, so we immediately continue, before it
+ // can get sent to other listeners below.
+ if (DBG) {
+ Slog.d(TAG, "prepareNotifyPostedLocked: direct reply update, "
+ + "skipping post to " + info.toString());
+ }
+ continue;
+ }
}
}
@@ -12837,9 +13535,13 @@ public class NotificationManagerService extends SystemService {
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener) info.service;
- StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
- listener.onNotificationPosted(sbnHolder, rankingUpdate);
+ if (android.app.Flags.noSbnholder()) {
+ listener.onNotificationPostedFull(sbn, rankingUpdate);
+ } else {
+ StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
+ listener.onNotificationPosted(sbnHolder, rankingUpdate);
+ }
} catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
} catch (RemoteException ex) {
@@ -12850,7 +13552,6 @@ public class NotificationManagerService extends SystemService {
private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
final INotificationListener listener = (INotificationListener) info.service;
- StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
if (!CompatChanges.isChangeEnabled(NOTIFICATION_CANCELLATION_REASONS, info.uid)
&& (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) {
@@ -12862,7 +13563,12 @@ public class NotificationManagerService extends SystemService {
&& reason == REASON_ASSISTANT_CANCEL) {
reason = REASON_LISTENER_CANCEL;
}
- listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+ if (android.app.Flags.noSbnholder()) {
+ listener.onNotificationRemovedFull(sbn, rankingUpdate, stats, reason);
+ } else {
+ StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
+ listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+ }
} catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b9f0968b5864..93f512bc7e17 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -334,7 +334,7 @@ public final class NotificationRecord {
return helper.createWaveformVibration(vibrationPattern, insistent);
}
- if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) {
+ if (com.android.server.notification.Flags.notificationVibrationInSoundUriForChannel()) {
final VibrationEffect vibrationEffectFromSoundUri =
helper.createVibrationEffectFromSoundUri(channel.getSound());
if (vibrationEffectFromSoundUri != null) {
@@ -1493,14 +1493,23 @@ public final class NotificationRecord {
final Notification notification = getNotification();
notification.visitUris((uri) -> {
- visitGrantableUri(uri, false, false);
+ if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
+ visitGrantableUri(uri, false, false);
+ } else {
+ oldVisitGrantableUri(uri, false, false);
+ }
});
if (notification.getChannelId() != null) {
NotificationChannel channel = getChannel();
if (channel != null) {
- visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
+ visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ } else {
+ oldVisitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ }
}
}
} finally {
@@ -1516,7 +1525,7 @@ public final class NotificationRecord {
* {@link #mGrantableUris}. Otherwise, this will either log or throw
* {@link SecurityException} depending on target SDK of enqueuing app.
*/
- private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
+ private void oldVisitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
if (mGrantableUris != null && mGrantableUris.contains(uri)) {
@@ -1555,6 +1564,45 @@ public final class NotificationRecord {
}
}
+ /**
+ * Note the presence of a {@link Uri} that should have permission granted to
+ * whoever will be rendering it.
+ * <p>
+ * If the enqueuing app has the ability to grant access, it will be added to
+ * {@link #mGrantableUris}. Otherwise, this will either log or throw
+ * {@link SecurityException} depending on target SDK of enqueuing app.
+ */
+ private void visitGrantableUri(Uri uri, boolean userOverriddenUri,
+ boolean isSound) {
+ if (mGrantableUris != null && mGrantableUris.contains(uri)) {
+ return; // already verified this URI
+ }
+
+ final int sourceUid = getSbn().getUid();
+ try {
+ PermissionHelper.grantUriPermission(mUgmInternal, uri, sourceUid);
+
+ if (mGrantableUris == null) {
+ mGrantableUris = new ArraySet<>();
+ }
+ mGrantableUris.add(uri);
+ } catch (SecurityException e) {
+ if (!userOverriddenUri) {
+ if (isSound) {
+ mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
+ Log.w(TAG, "Replacing " + uri + " from " + sourceUid + ": " + e.getMessage());
+ } else {
+ if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
+ throw e;
+ } else {
+ Log.w(TAG,
+ "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+
public LogMaker getLogMaker(long now) {
LogMaker lm = getSbn().getLogMaker()
.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 65ef53f1df14..3943aa583fee 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -313,7 +313,11 @@ interface NotificationRecordLogger {
@UiEvent(doc = "Notification assistant generated notification action at 1 was clicked.")
NOTIFICATION_ASSIST_ACTION_CLICKED_1(457),
@UiEvent(doc = "Notification assistant generated notification action at 2 was clicked.")
- NOTIFICATION_ASSIST_ACTION_CLICKED_2(458);
+ NOTIFICATION_ASSIST_ACTION_CLICKED_2(458),
+ @UiEvent(doc = "Notification was force autogrouped.")
+ NOTIFICATION_FORCE_GROUP(1843),
+ @UiEvent(doc = "Notification summary was force autogrouped.")
+ NOTIFICATION_FORCE_GROUP_SUMMARY(1844);
private final int mId;
NotificationEvent(int id) {
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 10169d544b73..c305d66c24c1 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -80,6 +80,7 @@ public class NotificationShellCmd extends ShellCommand {
+ " get <notification-key>\n"
+ " snooze --for <msec> <notification-key>\n"
+ " unsnooze <notification-key>\n"
+ + " set_exempt_th_force_grouping [true|false]\n"
;
private static final String NOTIFY_USAGE =
@@ -428,6 +429,13 @@ public class NotificationShellCmd extends ShellCommand {
}
break;
}
+ case "set_exempt_th_force_grouping": {
+ String arg = getNextArgRequired();
+ final boolean exemptTestHarnessFromForceGrouping =
+ "true".equals(arg) || "1".equals(arg);
+ mDirectService.setTestHarnessExempted(exemptTestHarnessFromForceGrouping);
+ break;
+ }
default:
return handleDefaultCommands(cmd);
}
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index f0358d1e1d8c..be34beeb1236 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -55,4 +55,9 @@ public interface NotificationSignalExtractor {
void setZenHelper(ZenModeHelper helper);
default void setCompatChangeLogger(IPlatformCompat platformCompat){};
+
+ /**
+ * @param groupHelper Helper for auto-grouping notifications
+ */
+ default void setGroupHelper(GroupHelper groupHelper){};
}
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index c09077e349fd..f17ac5c92889 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.service.notification.RateEstimator;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index b6f48890c528..1464d481311a 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,19 +25,25 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.companion.virtual.VirtualDeviceManager;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.server.uri.UriGrantsManagerInternal;
import java.util.Collections;
import java.util.HashSet;
@@ -58,7 +64,7 @@ public final class PermissionHelper {
private final IPermissionManager mPermManager;
public PermissionHelper(Context context, IPackageManager packageManager,
- IPermissionManager permManager) {
+ IPermissionManager permManager) {
mContext = context;
mPackageManager = packageManager;
mPermManager = permManager;
@@ -298,6 +304,19 @@ public final class PermissionHelper {
return false;
}
+ static void grantUriPermission(final UriGrantsManagerInternal ugmInternal, Uri uri,
+ int sourceUid) {
+ if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+
+ Binder.withCleanCallingIdentity(() -> {
+ // This will throw a SecurityException if the caller can't grant.
+ ugmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
+ });
+ }
+
public static class PackagePermission {
public final String packageName;
public final @UserIdInt int userId;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index fcc8d2f74ce9..749952e3336f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
import static android.app.NotificationChannel.PROMOTIONS_ID;
@@ -32,6 +33,10 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.notificationClassification;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
@@ -40,6 +45,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -51,6 +57,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.ZenBypassingApp;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -65,6 +72,7 @@ import android.os.Process;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.provider.Settings;
+import android.service.notification.Adjustment;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
import android.service.notification.RankingHelperProto;
@@ -93,6 +101,7 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.PermissionHelper.PackagePermission;
+import com.android.server.uri.UriGrantsManagerInternal;
import org.json.JSONArray;
import org.json.JSONException;
@@ -175,6 +184,7 @@ public class PreferencesHelper implements RankingConfig {
private static final boolean DEFAULT_SHOW_BADGE = true;
private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false;
+ private static final boolean DEFAULT_CAN_HAVE_PROMOTED_NOTIFS = true;
static final boolean DEFAULT_BUBBLES_ENABLED = true;
@VisibleForTesting
@@ -192,10 +202,13 @@ public class PreferencesHelper implements RankingConfig {
/**
* All user-lockable fields for a given application.
*/
- @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
+ @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE,
+ LockableAppFields.USER_LOCKED_BUBBLE,
+ LockableAppFields.USER_LOCKED_PROMOTABLE})
public @interface LockableAppFields {
int USER_LOCKED_IMPORTANCE = 0x00000001;
int USER_LOCKED_BUBBLE = 0x00000002;
+ int USER_LOCKED_PROMOTABLE = 0x00000004;
}
private final Object mLock = new Object();
@@ -215,6 +228,7 @@ public class PreferencesHelper implements RankingConfig {
private final NotificationChannelLogger mNotificationChannelLogger;
private final AppOpsManager mAppOps;
private final ManagedServices.UserProfiles mUserProfiles;
+ private final UriGrantsManagerInternal mUgmInternal;
private SparseBooleanArray mBadgingEnabled;
private SparseBooleanArray mBubblesEnabled;
@@ -235,6 +249,7 @@ public class PreferencesHelper implements RankingConfig {
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
+ UriGrantsManagerInternal ugmInternal,
boolean showReviewPermissionsNotification, Clock clock) {
mContext = context;
mZenModeHelper = zenHelper;
@@ -245,6 +260,7 @@ public class PreferencesHelper implements RankingConfig {
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
mUserProfiles = userProfiles;
+ mUgmInternal = ugmInternal;
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
@@ -354,8 +370,8 @@ public class PreferencesHelper implements RankingConfig {
null, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false);
if (android.app.Flags.uiRichOngoing()) {
- r.canHavePromotedNotifs =
- parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, false);
+ r.canHavePromotedNotifs = parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS,
+ DEFAULT_CAN_HAVE_PROMOTED_NOTIFS);
}
final int innerDepth = parser.getDepth();
@@ -432,6 +448,10 @@ public class PreferencesHelper implements RankingConfig {
PackagePreferences r) {
try {
String id = parser.getAttributeValue(null, ATT_ID);
+ if (!notificationClassification() && SYSTEM_RESERVED_IDS.contains(id)) {
+ // delete bundle channels if flag is rolled back
+ return;
+ }
String channelName = parser.getAttributeValue(null, ATT_NAME);
int channelImportance = parser.getAttributeInt(
null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
@@ -537,10 +557,6 @@ public class PreferencesHelper implements RankingConfig {
Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
}
- if (notificationClassification()) {
- addReservedChannelsLocked(r);
- }
-
if (r.uid == UNKNOWN_UID) {
if (Flags.persistIncompleteRestoreData()) {
r.userId = userId;
@@ -575,7 +591,7 @@ public class PreferencesHelper implements RankingConfig {
private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws
PackageManager.NameNotFoundException {
- if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ if (!r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
// Not present
return false;
}
@@ -586,7 +602,7 @@ public class PreferencesHelper implements RankingConfig {
}
// Remove Default Channel.
- r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+ r.channels.remove(DEFAULT_CHANNEL_ID);
return true;
}
@@ -597,8 +613,8 @@ public class PreferencesHelper implements RankingConfig {
return false;
}
- if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
+ if (r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
+ r.channels.get(DEFAULT_CHANNEL_ID).setName(mContext.getString(
com.android.internal.R.string.default_notification_channel_label));
return false;
}
@@ -611,7 +627,7 @@ public class PreferencesHelper implements RankingConfig {
// Create Default Channel
NotificationChannel channel;
channel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID,
+ DEFAULT_CHANNEL_ID,
mContext.getString(R.string.default_notification_channel_label),
r.importance);
channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
@@ -630,38 +646,25 @@ public class PreferencesHelper implements RankingConfig {
return true;
}
- private void addReservedChannelsLocked(PackagePreferences p) {
- if (!p.channels.containsKey(NotificationChannel.PROMOTIONS_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.PROMOTIONS_ID,
- mContext.getString(R.string.promotional_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
- }
-
- if (!p.channels.containsKey(NotificationChannel.RECS_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.RECS_ID,
- mContext.getString(R.string.recs_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
- }
-
- if (!p.channels.containsKey(NotificationChannel.NEWS_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.NEWS_ID,
- mContext.getString(R.string.news_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
- }
-
- if (!p.channels.containsKey(NotificationChannel.SOCIAL_MEDIA_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.SOCIAL_MEDIA_ID,
- mContext.getString(R.string.social_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
+ private NotificationChannel addReservedChannelLocked(PackagePreferences p, String channelId) {
+ String label = "";
+ switch (channelId) {
+ case PROMOTIONS_ID:
+ label = mContext.getString(R.string.promotional_notification_channel_label);
+ break;
+ case RECS_ID:
+ label = mContext.getString(R.string.recs_notification_channel_label);
+ break;
+ case NEWS_ID:
+ label = mContext.getString(R.string.news_notification_channel_label);
+ break;
+ case SOCIAL_MEDIA_ID:
+ label = mContext.getString(R.string.social_notification_channel_label);
+ break;
}
+ NotificationChannel channel = new NotificationChannel(channelId, label, IMPORTANCE_LOW);
+ p.channels.put(channelId, channel);
+ return channel;
}
public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
@@ -746,7 +749,7 @@ public class PreferencesHelper implements RankingConfig {
r.userDemotedMsgApp);
out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
if (android.app.Flags.uiRichOngoing()) {
- if (r.canHavePromotedNotifs) {
+ if (r.canHavePromotedNotifs != DEFAULT_CAN_HAVE_PROMOTED_NOTIFS) {
out.attributeBoolean(null, ATT_PROMOTE_NOTIFS, r.canHavePromotedNotifs);
}
}
@@ -850,21 +853,27 @@ public class PreferencesHelper implements RankingConfig {
}
}
- @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
public boolean canBePromoted(String packageName, int uid) {
synchronized (mLock) {
return getOrCreatePackagePreferencesLocked(packageName, uid).canHavePromotedNotifs;
}
}
- @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
- public boolean setCanBePromoted(String packageName, int uid, boolean promote) {
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ public boolean setCanBePromoted(String packageName, int uid, boolean promote,
+ boolean fromUser) {
boolean changed = false;
synchronized (mLock) {
PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
- if (pkgPrefs.canHavePromotedNotifs != promote) {
- pkgPrefs.canHavePromotedNotifs = promote;
- changed = true;
+ if (fromUser || ((pkgPrefs.lockedAppFields & USER_LOCKED_PROMOTABLE) == 0)) {
+ if (pkgPrefs.canHavePromotedNotifs != promote) {
+ pkgPrefs.canHavePromotedNotifs = promote;
+ if (fromUser) {
+ pkgPrefs.lockedAppFields |= USER_LOCKED_PROMOTABLE;
+ }
+ changed = true;
+ }
}
}
// no need to send a ranking update because we need to update the flag value on all pending
@@ -1060,7 +1069,7 @@ public class PreferencesHelper implements RankingConfig {
if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
}
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
+ if (DEFAULT_CHANNEL_ID.equals(channel.getId())) {
throw new IllegalArgumentException("Reserved id");
}
// Only the user can update bundle channel settings
@@ -1159,6 +1168,13 @@ public class PreferencesHelper implements RankingConfig {
}
clearLockedFieldsLocked(channel);
+ // Verify that the app has permission to read the sound Uri
+ // Only check for new channels, as regular apps can only set sound
+ // before creating. See: {@link NotificationChannel#setSound}
+ if (Flags.notificationVerifyChannelSoundUri()) {
+ PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
+ }
+
channel.setImportanceLockedByCriticalDeviceFunction(
r.defaultAppLockedImportance || r.fixedImportance);
@@ -1386,6 +1402,54 @@ public class PreferencesHelper implements RankingConfig {
}
}
+ private @Nullable String getChannelIdForBundleType(@Adjustment.Types int type) {
+ switch (type) {
+ case TYPE_CONTENT_RECOMMENDATION:
+ return RECS_ID;
+ case TYPE_NEWS:
+ return NEWS_ID;
+ case TYPE_PROMOTION:
+ return PROMOTIONS_ID;
+ case TYPE_SOCIAL_MEDIA:
+ return SOCIAL_MEDIA_ID;
+ }
+ return null;
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public NotificationChannel getReservedChannel(String pkg, int uid,
+ @Adjustment.Types int type) {
+ if (!notificationClassification()) {
+ return null;
+ }
+ Objects.requireNonNull(pkg);
+ String channelId = getChannelIdForBundleType(type);
+ if (channelId == null) {
+ return null;
+ }
+ NotificationChannel channel =
+ getConversationNotificationChannel(pkg, uid, channelId, null, true, false);
+ return channel;
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public NotificationChannel createReservedChannel(String pkg, int uid,
+ @Adjustment.Types int type) {
+ if (!notificationClassification()) {
+ return null;
+ }
+ Objects.requireNonNull(pkg);
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+ if (r == null) {
+ return null;
+ }
+ String channelId = getChannelIdForBundleType(type);
+ if (channelId == null) {
+ return null;
+ }
+ return addReservedChannelLocked(r, channelId);
+ }
+
@Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
@@ -1404,7 +1468,7 @@ public class PreferencesHelper implements RankingConfig {
return null;
}
if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+ channelId = DEFAULT_CHANNEL_ID;
}
NotificationChannel channel = null;
if (conversationId != null) {
@@ -1515,7 +1579,7 @@ public class PreferencesHelper implements RankingConfig {
int N = r.channels.size() - 1;
for (int i = N; i >= 0; i--) {
String key = r.channels.keyAt(i);
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+ if (!DEFAULT_CHANNEL_ID.equals(key)) {
r.channels.remove(key);
}
}
@@ -1633,10 +1697,7 @@ public class PreferencesHelper implements RankingConfig {
&& (activeChannelFilter == null
|| (includeBlocked && nc.getImportance() == IMPORTANCE_NONE)
|| activeChannelFilter.contains(nc.getId()))
- && !PROMOTIONS_ID.equals(nc.getId())
- && !NEWS_ID.equals(nc.getId())
- && !SOCIAL_MEDIA_ID.equals(nc.getId())
- && !RECS_ID.equals(nc.getId());
+ && !SYSTEM_RESERVED_IDS.contains(nc.getId());
if (includeChannel) {
if (nc.getGroup() != null) {
if (r.groups.get(nc.getGroup()) != null) {
@@ -1849,7 +1910,7 @@ public class PreferencesHelper implements RankingConfig {
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
- boolean includeDeleted) {
+ boolean includeDeleted, boolean includeBundles) {
Objects.requireNonNull(pkg);
List<NotificationChannel> channels = new ArrayList<>();
synchronized (mLock) {
@@ -1861,7 +1922,9 @@ public class PreferencesHelper implements RankingConfig {
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
- channels.add(nc);
+ if (includeBundles || !SYSTEM_RESERVED_IDS.contains(nc.getId())) {
+ channels.add(nc);
+ }
}
}
return new ParceledListSlice<>(channels);
@@ -1889,6 +1952,35 @@ public class PreferencesHelper implements RankingConfig {
}
/**
+ * Gets all apps that can bypass DND, and a boolean indicating whether all (true) or some
+ * (false) of its notification channels can currently bypass.
+ */
+ public @NonNull ArrayList<ZenBypassingApp> getPackagesBypassingDnd(@UserIdInt int userId) {
+ ArrayList<ZenBypassingApp> bypassing = new ArrayList<>();
+ synchronized (mLock) {
+ for (PackagePreferences p : mPackagePreferences.values()) {
+ if (p.userId != userId) {
+ continue;
+ }
+ int totalChannelCount = p.channels.size();
+ int bypassingCount = 0;
+ if (totalChannelCount == 0) {
+ continue;
+ }
+ for (NotificationChannel channel : p.channels.values()) {
+ if (channelIsLiveLocked(p, channel) && channel.canBypassDnd()) {
+ bypassingCount++;
+ }
+ }
+ if (bypassingCount > 0) {
+ bypassing.add(new ZenBypassingApp(p.pkg, totalChannelCount == bypassingCount));
+ }
+ }
+ }
+ return bypassing;
+ }
+
+ /**
* True for pre-O apps that only have the default channel, or pre O apps that have no
* channels yet. This method will create the default channel for pre-O apps that don't have it.
* Should never be true for O+ targeting apps, but that's enforced on boot/when an app
@@ -1897,9 +1989,23 @@ public class PreferencesHelper implements RankingConfig {
public boolean onlyHasDefaultChannel(String pkg, int uid) {
synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
- if (r.channels.size() == (notificationClassification() ? 5 : 1)
- && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- return true;
+ if (r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
+ if (r.channels.size() == 1) {
+ return true;
+ }
+ if (notificationClassification()) {
+ if (r.channels.size() <= 5) {
+ for (NotificationChannel c : r.channels.values()) {
+ if (!SYSTEM_RESERVED_IDS.contains(c.getId()) &&
+ !DEFAULT_CHANNEL_ID.equals(c.getId())) {
+ return false;
+ }
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
}
return false;
}
@@ -1953,8 +2059,9 @@ public class PreferencesHelper implements RankingConfig {
* </ul>
*/
void syncChannelsBypassingDnd() {
- mCurrentUserHasChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
- & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+ mCurrentUserHasChannelsBypassingDnd =
+ (mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).state
+ & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID,
/* fromSystemOrSystemUi= */ true);
@@ -1965,6 +2072,7 @@ public class PreferencesHelper implements RankingConfig {
* bypassing DND. It should be called whenever a channel is created, updated, or deleted, or
* when the current user (or its profiles) change.
*/
+ // TODO: b/368247671 - remove fromSystemOrSystemUi argument when modes_ui is inlined.
private void updateCurrentUserHasChannelsBypassingDnd(int callingUid,
boolean fromSystemOrSystemUi) {
ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
@@ -1995,7 +2103,13 @@ public class PreferencesHelper implements RankingConfig {
boolean haveBypassingApps = candidatePkgs.size() > 0;
if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) {
mCurrentUserHasChannelsBypassingDnd = haveBypassingApps;
- updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid, fromSystemOrSystemUi);
+ if (android.app.Flags.modesUi()) {
+ mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT,
+ mCurrentUserHasChannelsBypassingDnd);
+ } else {
+ updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid,
+ fromSystemOrSystemUi);
+ }
}
}
@@ -2013,10 +2127,15 @@ public class PreferencesHelper implements RankingConfig {
return true;
}
+ // TODO: b/368247671 - delete this method when modes_ui is inlined, as
+ // updateCurrentUserHasChannelsBypassingDnd was the only caller and
+ // PreferencesHelper should otherwise not need to modify actual policy
public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid,
boolean fromSystemOrSystemUi) {
- NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
+ NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy(
+ UserHandle.CURRENT);
mZenModeHelper.setNotificationPolicy(
+ UserHandle.CURRENT,
new NotificationManager.Policy(
policy.priorityCategories, policy.priorityCallSenders,
policy.priorityMessageSenders, policy.suppressedVisualEffects,
@@ -2213,7 +2332,8 @@ public class PreferencesHelper implements RankingConfig {
pw.print(" fixedImportance=");
pw.print(r.fixedImportance);
}
- if (android.app.Flags.uiRichOngoing() && r.canHavePromotedNotifs) {
+ if (android.app.Flags.uiRichOngoing()
+ && r.canHavePromotedNotifs != DEFAULT_CAN_HAVE_PROMOTED_NOTIFS) {
pw.print(" promoted=");
pw.print(r.canHavePromotedNotifs);
}
@@ -2708,9 +2828,9 @@ public class PreferencesHelper implements RankingConfig {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
if (PackagePreferences.channels.containsKey(
- NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ DEFAULT_CHANNEL_ID)) {
PackagePreferences.channels.get(
- NotificationChannel.DEFAULT_CHANNEL_ID).setName(
+ DEFAULT_CHANNEL_ID).setName(
context.getResources().getString(
R.string.default_notification_channel_label));
}
@@ -3065,8 +3185,9 @@ public class PreferencesHelper implements RankingConfig {
boolean migrateToPm = false;
long creationTime;
- @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
- boolean canHavePromotedNotifs = false;
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ // Until we enable the UI, we should return false.
+ boolean canHavePromotedNotifs = android.app.Flags.uiRichOngoing();
@UserIdInt int userId;
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 8df24c9911a6..001e91cbea12 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -55,5 +55,5 @@ public interface RankingConfig {
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
- boolean includeDeleted);
+ boolean includeDeleted, boolean includeBundles);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 03dd9351efc7..f06d6405b3c2 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,7 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.text.TextUtils.formatSimple;
import android.annotation.NonNull;
-import android.app.NotificationManager;
import android.content.Context;
import android.service.notification.RankingHelperProto;
import android.util.ArrayMap;
@@ -61,7 +60,7 @@ public class RankingHelper {
})
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
- IPlatformCompat platformCompat) {
+ IPlatformCompat platformCompat, GroupHelper groupHelper) {
mContext = context;
mRankingHandler = rankingHandler;
if (sortSectionByTime()) {
@@ -80,6 +79,7 @@ public class RankingHelper {
extractor.initialize(mContext, usageStats);
extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
+ extractor.setGroupHelper(groupHelper);
if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
|| restrictAudioAttributesCall()) {
extractor.setCompatChangeLogger(platformCompat);
diff --git a/services/core/java/com/android/server/notification/RateEstimator.java b/services/core/java/com/android/server/notification/RateEstimator.java
deleted file mode 100644
index eda96ac84b16..000000000000
--- a/services/core/java/com/android/server/notification/RateEstimator.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.notification;
-
-
-/**
- * Exponentially weighted moving average estimator for event rate.
- *
- * {@hide}
- */
-class RateEstimator {
- private static final double RATE_ALPHA = 0.7;
- private static final double MINIMUM_DT = 0.0005;
-
- private Long mLastEventTime;
- private double mInterarrivalTime;
-
- public RateEstimator() {
- // assume something generous if we have no information
- mInterarrivalTime = 1000.0;
- }
-
- /** Update the estimate to account for an event that just happened. */
- public void update(long now) {
- if (mLastEventTime != null) {
- // Calculate the new inter-arrival time based on last event time.
- mInterarrivalTime = getInterarrivalEstimate(now);
- }
- mLastEventTime = now;
- }
-
- /** @return the estimated rate if there were a new event right now. */
- public float getRate(long now) {
- if (mLastEventTime == null) {
- return 0f;
- }
- return (float) (1.0 / getInterarrivalEstimate(now));
- }
-
- /** @return the average inter-arrival time if there were a new event right now. */
- private double getInterarrivalEstimate(long now) {
- double dt = ((double) (now - mLastEventTime)) / 1000.0;
- dt = Math.max(dt, MINIMUM_DT);
- // a*iat_old + (1-a)*(t_now-t_last)
- return (RATE_ALPHA * mInterarrivalTime + (1.0 - RATE_ALPHA) * dt);
- }
-}
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 6efe88f6a155..734c61b6b2a8 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Binder;
+import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ScheduleCalendar;
@@ -41,6 +42,7 @@ import com.android.server.notification.NotificationManagerService.DumpFilter;
import com.android.server.pm.PackageManagerService;
import java.io.PrintWriter;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@@ -61,6 +63,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
private static final String SCP_SETTING = "snoozed_schedule_condition_provider";
private final Context mContext = this;
+ private final Clock mClock;
private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>();
@GuardedBy("mSnoozedForAlarm")
private final ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>();
@@ -71,7 +74,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
private long mNextAlarmTime;
public ScheduleConditionProvider() {
+ this(Clock.systemUTC());
+ }
+
+ @VisibleForTesting
+ ScheduleConditionProvider(Clock clock) {
if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
+ mClock = clock;
}
@Override
@@ -85,7 +94,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
pw.print(" mConnected="); pw.println(mConnected);
pw.print(" mRegistered="); pw.println(mRegistered);
pw.println(" mSubscriptions=");
- final long now = System.currentTimeMillis();
+ final long now = mClock.millis();
synchronized (mSubscriptions) {
for (Uri conditionId : mSubscriptions.keySet()) {
pw.print(" ");
@@ -115,6 +124,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
}
@Override
+ public void onUserSwitched(UserHandle user) {
+ // Nothing to do here because evaluateSubscriptions() is called for the new configuration
+ // when users switch, and that will reevaluate the next alarm, which is the only piece that
+ // is user-dependent.
+ }
+
+ @Override
public void onDestroy() {
super.onDestroy();
if (DEBUG) Slog.d(TAG, "onDestroy");
@@ -145,12 +161,9 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
}
private void evaluateSubscriptions() {
- if (mAlarmManager == null) {
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- }
- final long now = System.currentTimeMillis();
+ final long now = mClock.millis();
mNextAlarmTime = 0;
- long nextUserAlarmTime = getNextAlarm();
+ long nextUserAlarmTime = getNextAlarmClockAlarm();
List<Condition> conditionsToNotify = new ArrayList<>();
synchronized (mSubscriptions) {
setRegistered(!mSubscriptions.isEmpty());
@@ -226,7 +239,10 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
- public long getNextAlarm() {
+ private long getNextAlarmClockAlarm() {
+ if (mAlarmManager == null) {
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ }
final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(
ActivityManager.getCurrentUser());
return info != null ? info.getTriggerTime() : 0;
@@ -246,8 +262,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(ACTION_EVALUATE);
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- registerReceiver(mReceiver, filter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ if (android.app.Flags.modesHsum()) {
+ registerReceiverForAllUsers(mReceiver, filter, /* broadcastPermission= */ null,
+ /* scheduler= */ null);
+ } else {
+ registerReceiver(mReceiver, filter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
} else {
unregisterReceiver(mReceiver);
}
@@ -321,10 +342,18 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
}
}
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
+ if (android.app.Flags.modesHsum()) {
+ if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(intent.getAction())
+ && getSendingUserId() != ActivityManager.getCurrentUser()) {
+ // A different user changed their next alarm.
+ return;
+ }
+ }
+
if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
synchronized (mSubscriptions) {
for (Uri conditionId : mSubscriptions.keySet()) {
@@ -339,4 +368,8 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
}
};
+ @VisibleForTesting // otherwise = NONE
+ public ArrayMap<Uri, ScheduleCalendar> getSubscriptions() {
+ return mSubscriptions;
+ }
}
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
index 97073b77f1f7..656f9dfd0917 100644
--- a/services/core/java/com/android/server/notification/SystemConditionProviderService.java
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -19,6 +19,7 @@ package com.android.server.notification;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
+import android.os.UserHandle;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
import android.util.TimeUtils;
@@ -30,9 +31,10 @@ import java.util.Date;
public abstract class SystemConditionProviderService extends ConditionProviderService {
- abstract public void dump(PrintWriter pw, DumpFilter filter);
- abstract public boolean isValidConditionId(Uri id);
- abstract public void onBootComplete();
+ public abstract void dump(PrintWriter pw, DumpFilter filter);
+ public abstract boolean isValidConditionId(Uri id);
+ public abstract void onBootComplete();
+ public abstract void onUserSwitched(UserHandle user);
final ComponentName getComponent() {
return new ComponentName("android", this.getClass().getName());
diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
index cabe766289f7..b053dfe35a90 100644
--- a/services/core/java/com/android/server/notification/TimeToLiveHelper.java
+++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
@@ -188,7 +188,11 @@ public class TimeToLiveHelper {
timeoutKey = earliest.second;
}
}
- mNm.timeoutNotification(timeoutKey);
+ if (timeoutKey != null) {
+ mNm.timeoutNotification(timeoutKey);
+ } else {
+ Slog.wtf(TAG, "Alarm triggered but should have been cleaned up already");
+ }
}
}
};
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 50bfbc3530a9..52d0c41614d5 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.net.Uri;
import android.os.Binder;
import android.os.Process;
+import android.os.UserHandle;
import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
@@ -102,16 +103,6 @@ public class ZenModeConditions implements ConditionProviders.Callback {
}
@Override
- public void onBootComplete() {
- // noop
- }
-
- @Override
- public void onUserSwitched() {
- // noop
- }
-
- @Override
public void onServiceAdded(ComponentName component) {
if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
final int callingUid = Binder.getCallingUid();
@@ -127,7 +118,10 @@ public class ZenModeConditions implements ConditionProviders.Callback {
ZenModeConfig config = mHelper.getConfig();
if (config == null) return;
final int callingUid = Binder.getCallingUid();
- mHelper.setAutomaticZenRuleState(id, condition,
+
+ // This change is known to be for UserHandle.CURRENT because ConditionProviders for
+ // background users are not bound.
+ mHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, condition,
callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
: ZenModeConfig.ORIGIN_APP,
callingUid);
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index ff263d1467c3..bdca555707e3 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -37,7 +37,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.NotificationMessagingUtil;
import java.io.PrintWriter;
@@ -173,13 +172,6 @@ public class ZenModeFiltering {
maybeLogInterceptDecision(record, false, "criticalNotification");
return false;
}
- // Make an exception to policy for the notification saying that policy has changed
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
- && "android".equals(record.getSbn().getPackageName())
- && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
- maybeLogInterceptDecision(record, false, "systemDndChangedNotification");
- return false;
- }
switch (zen) {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
// #notevenalarms
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 626c3ddd49d9..95aff5652bb6 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -24,6 +24,8 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static android.service.notification.Condition.SOURCE_UNKNOWN;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_FALSE;
@@ -40,6 +42,7 @@ import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
import static android.service.notification.ZenModeConfig.implicitRuleId;
+import static android.service.notification.ZenModeConfig.isImplicitRuleId;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -54,10 +57,9 @@ import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
-import android.app.PendingIntent;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -74,7 +76,6 @@ import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -90,7 +91,6 @@ import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
@@ -112,13 +112,12 @@ import android.util.SparseArray;
import android.util.StatsEvent;
import android.util.proto.ProtoOutputStream;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -173,15 +172,13 @@ public class ZenModeHelper {
private final Clock mClock;
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
- private final NotificationManager mNotificationManager;
private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final ZenModeFiltering mFiltering;
private final RingerModeDelegate mRingerModeDelegate = new
RingerModeDelegate();
@VisibleForTesting protected final ZenModeConditions mConditions;
- private final Object mConfigsArrayLock = new Object();
- @GuardedBy("mConfigsArrayLock")
+ @GuardedBy("mConfigLock")
@VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
private final Metrics mMetrics = new Metrics();
private final ConditionProviders.Config mServiceConfig;
@@ -221,15 +218,14 @@ public class ZenModeHelper {
mClock = clock;
addCallback(mMetrics);
mAppOps = context.getSystemService(AppOpsManager.class);
- mNotificationManager = context.getSystemService(NotificationManager.class);
mDefaultConfig = Flags.modesUi()
? ZenModeConfig.getDefaultConfig()
: readDefaultConfig(mContext.getResources());
updateDefaultConfig(mContext, mDefaultConfig);
- mConfig = mDefaultConfig.copy();
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
+ mConfig = mDefaultConfig.copy();
mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
}
mConsolidatedPolicy = mConfig.toNotificationPolicy();
@@ -243,10 +239,6 @@ public class ZenModeHelper {
mZenModeEventLogger = zenModeEventLogger;
}
- public Looper getLooper() {
- return mHandler.getLooper();
- }
-
@Override
public String toString() {
return TAG;
@@ -288,6 +280,11 @@ public class ZenModeHelper {
mCallbacks.remove(callback);
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public List<Callback> getCallbacks() {
+ return mCallbacks;
+ }
+
public void initZenMode() {
if (DEBUG) Log.d(TAG, "initZenMode");
synchronized (mConfigLock) {
@@ -309,7 +306,15 @@ public class ZenModeHelper {
mHandler.postMetricsTimer();
cleanUpZenRules();
mIsSystemServicesReady = true;
- showZenUpgradeNotification(mZenMode);
+ }
+
+ /**
+ * @return whether a {@link DeviceEffectsApplier} has already been set or not
+ */
+ boolean hasDeviceEffectsApplier() {
+ synchronized (mConfigLock) {
+ return mDeviceEffectsApplier != null;
+ }
}
/**
@@ -338,7 +343,7 @@ public class ZenModeHelper {
public void onUserRemoved(int user) {
if (user < UserHandle.USER_SYSTEM) return;
if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user);
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
mConfigs.remove(user);
}
}
@@ -357,7 +362,7 @@ public class ZenModeHelper {
mUser = user;
if (DEBUG) Log.d(TAG, reason + " u=" + user);
ZenModeConfig config = null;
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
if (mConfigs.get(user) != null) {
config = mConfigs.get(user).copy();
}
@@ -383,7 +388,9 @@ public class ZenModeHelper {
boolean fromSystemOrSystemUi) {
final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (newZen != -1) {
- setManualZenMode(newZen, null,
+ // This change is known to be for UserHandle.CURRENT because NLSes for
+ // background users are unbound.
+ setManualZenMode(UserHandle.CURRENT, newZen, null,
fromSystemOrSystemUi ? ORIGIN_SYSTEM : ORIGIN_APP,
/* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
/* caller= */ name != null ? name.getPackageName() : null,
@@ -406,11 +413,12 @@ public class ZenModeHelper {
}
// TODO: b/310620812 - Make private (or inline) when MODES_API is inlined.
- public List<ZenRule> getZenRules() {
+ public List<ZenRule> getZenRules(UserHandle user) {
List<ZenRule> rules = new ArrayList<>();
synchronized (mConfigLock) {
- if (mConfig == null) return rules;
- for (ZenRule rule : mConfig.automaticRules.values()) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return rules;
+ for (ZenRule rule : config.automaticRules.values()) {
if (canManageAutomaticZenRule(rule)) {
rules.add(rule);
}
@@ -424,8 +432,8 @@ public class ZenModeHelper {
* (which means the owned rules for a regular app, and every rule for system callers) together
* with their ids.
*/
- Map<String, AutomaticZenRule> getAutomaticZenRules() {
- List<ZenRule> ruleList = getZenRules();
+ Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user) {
+ List<ZenRule> ruleList = getZenRules(user);
HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size());
for (ZenRule rule : ruleList) {
rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
@@ -433,11 +441,12 @@ public class ZenModeHelper {
return rules;
}
- public AutomaticZenRule getAutomaticZenRule(String id) {
+ public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id) {
ZenRule rule;
synchronized (mConfigLock) {
- if (mConfig == null) return null;
- rule = mConfig.automaticRules.get(id);
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return null;
+ rule = config.automaticRules.get(id);
}
if (rule == null) return null;
if (canManageAutomaticZenRule(rule)) {
@@ -446,8 +455,9 @@ public class ZenModeHelper {
return null;
}
- public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
- @ConfigOrigin int origin, String reason, int callingUid) {
+ public String addAutomaticZenRule(UserHandle user, String pkg,
+ AutomaticZenRule automaticZenRule, @ConfigOrigin int origin, String reason,
+ int callingUid) {
checkManageRuleOrigin("addAutomaticZenRule", origin);
if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
@@ -462,10 +472,10 @@ public class ZenModeHelper {
ruleInstanceLimit = component.metaData.getInt(
ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
}
- int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
- + getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ int newRuleInstanceCount = getCurrentInstanceCount(user, automaticZenRule.getOwner())
+ + getCurrentInstanceCount(user, automaticZenRule.getConfigurationActivity())
+ 1;
- int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ int newPackageRuleCount = getPackageRuleCount(user, pkg) + 1;
if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
@@ -474,18 +484,19 @@ public class ZenModeHelper {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
throw new AndroidRuntimeException("Could not create rule");
}
if (DEBUG) {
Log.d(TAG, "addAutomaticZenRule rule= " + automaticZenRule + " reason=" + reason);
}
- newConfig = mConfig.copy();
+ newConfig = config.copy();
ZenRule rule = new ZenRule();
- populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
+ populateZenRule(pkg, automaticZenRule, newConfig, rule, origin, /* isNew= */ true);
rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
- maybeReplaceDefaultRule(newConfig, automaticZenRule);
+ maybeReplaceDefaultRule(newConfig, null, automaticZenRule);
if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
return rule.id;
@@ -531,17 +542,28 @@ public class ZenModeHelper {
// "Preserve" the previous rule by considering the azrToAdd an update instead.
// Only app-modifiable fields will actually be modified.
- populateZenRule(pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+ populateZenRule(pkg, azrToAdd, config, ruleToRestore, origin, /* isNew= */ false);
return ruleToRestore;
}
- private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
+ /**
+ * Possibly delete built-in rules if a more suitable rule is added or updated.
+ *
+ * <p>Today, this is done in one case: delete a disabled "Sleeping" rule if a Bedtime Mode is
+ * added (or an existing mode is turned into {@link AutomaticZenRule#TYPE_BEDTIME}, when
+ * upgrading). Because only the {@code config_systemWellbeing} package is allowed to use rules
+ * of this type, this will not trigger wantonly.
+ *
+ * @param oldRule If non-null, {@code rule} is updating {@code oldRule}. Otherwise,
+ * {@code rule} is being added.
+ */
+ private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule,
+ AutomaticZenRule rule) {
if (!Flags.modesApi()) {
return;
}
- if (addedRule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
- // Delete a built-in disabled "Sleeping" rule when a BEDTIME rule is added; it may have
- // smarter triggers and it will prevent confusion about which one to use.
+ if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME
+ && (oldRule == null || oldRule.type != rule.getType())) {
// Note: we must not verify canManageAutomaticZenRule here, since most likely they
// won't have the same owner (sleeping - system; bedtime - DWB).
ZenRule sleepingRule = config.automaticRules.get(
@@ -554,41 +576,47 @@ public class ZenModeHelper {
}
}
- public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
- @ConfigOrigin int origin, String reason, int callingUid) {
+ public boolean updateAutomaticZenRule(UserHandle user, String ruleId,
+ AutomaticZenRule automaticZenRule, @ConfigOrigin int origin, String reason,
+ int callingUid) {
checkManageRuleOrigin("updateAutomaticZenRule", origin);
if (ruleId == null) {
throw new IllegalArgumentException("ruleId cannot be null");
}
synchronized (mConfigLock) {
- if (mConfig == null) return false;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return false;
if (DEBUG) {
Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ " reason=" + reason);
}
- ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+ ZenModeConfig.ZenRule oldRule = config.automaticRules.get(ruleId);
if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
throw new SecurityException(
"Cannot update rules not owned by your condition provider");
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
if (!Flags.modesApi()) {
if (newRule.enabled != automaticZenRule.isEnabled()) {
- dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
+ dispatchOnAutomaticRuleStatusChanged(config.user, newRule.getPkg(), ruleId,
automaticZenRule.isEnabled()
? AUTOMATIC_RULE_STATUS_ENABLED
: AUTOMATIC_RULE_STATUS_DISABLED);
}
}
- boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+ boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newConfig, newRule,
origin, /* isNew= */ false);
if (Flags.modesApi() && !updated) {
// Bail out so we don't have the side effects of updating a rule (i.e. dropping
// condition) when no changes happen.
return true;
}
+
+ if (Flags.modesUi()) {
+ maybeReplaceDefaultRule(newConfig, oldRule, automaticZenRule);
+ }
return setConfigLocked(newConfig, origin, reason,
newRule.component, true, callingUid);
}
@@ -611,16 +639,18 @@ public class ZenModeHelper {
*
* @param zenMode one of the {@code Global#ZEN_MODE_x} values
*/
- void applyGlobalZenModeAsImplicitZenRule(String callingPkg, int callingUid, int zenMode) {
+ void applyGlobalZenModeAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid,
+ int zenMode) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalZenModeAsImplicitZenRule called with flag off!");
return;
}
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return;
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (zenMode == Global.ZEN_MODE_OFF) {
// Deactivate implicit rule if it exists and is active; otherwise ignore.
@@ -642,9 +672,14 @@ public class ZenModeHelper {
// would apply if changing the global interruption filter. We only do this
// for newly created rules, as existing rules have a pre-existing policy
// (whether initialized here or set via app or user).
- rule.zenPolicy = mConfig.getZenPolicy().copy();
+ rule.zenPolicy = config.getZenPolicy().copy();
newConfig.automaticRules.put(rule.id, rule);
+ } else {
+ if (Flags.modesUi()) {
+ updateImplicitZenRuleNameAndDescription(rule);
+ }
}
+
// If the user has changed the rule's *zenMode*, then don't let app overwrite it.
// We allow the update if the user has only changed other aspects of the rule.
if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
@@ -672,17 +707,18 @@ public class ZenModeHelper {
* {@link AutomaticZenRule#configurationActivity}. Its zen mode will be set to
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
- void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
+ void applyGlobalPolicyAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid,
NotificationManager.Policy policy) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
}
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return;
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
boolean isNew = false;
ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (rule == null) {
@@ -690,7 +726,12 @@ public class ZenModeHelper {
rule = newImplicitZenRule(callingPkg);
rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
newConfig.automaticRules.put(rule.id, rule);
+ } else {
+ if (Flags.modesUi()) {
+ updateImplicitZenRuleNameAndDescription(rule);
+ }
}
+
// If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
// We allow the update if the user has only changed other aspects of the rule.
if (rule.zenPolicyUserModifiedFields == 0) {
@@ -701,9 +742,10 @@ public class ZenModeHelper {
// would take effect if changing the global policy.
// Note that NotificationManager.Policy cannot have any unset priority
// categories, but *can* have unset visual effects, which is why we do this.
- newZenPolicy = mConfig.getZenPolicy().overwrittenWith(newZenPolicy);
+ newZenPolicy = config.getZenPolicy().overwrittenWith(newZenPolicy);
}
updatePolicy(
+ newConfig,
rule,
newZenPolicy,
/* updateBitmask= */ false,
@@ -726,25 +768,26 @@ public class ZenModeHelper {
* <p>Any unset values in the {@link ZenPolicy} will be mapped to their current defaults.
*/
@Nullable
- Policy getNotificationPolicyFromImplicitZenRule(String callingPkg) {
+ Policy getNotificationPolicyFromImplicitZenRule(UserHandle user, String callingPkg) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "getNotificationPolicyFromImplicitZenRule called with flag off!");
- return getNotificationPolicy();
+ return getNotificationPolicy(user);
}
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return null;
}
- ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
+ ZenRule implicitRule = config.automaticRules.get(implicitRuleId(callingPkg));
if (implicitRule != null && implicitRule.zenPolicy != null) {
- // toNotificationPolicy takes defaults from mConfig, and technically, those are not
+ // toNotificationPolicy takes defaults from config, and technically those are not
// the defaults that would apply if any fields were unset. However, all rules should
// have all fields set in their ZenPolicy objects upon rule creation, so in
// practice, this is only filling in the areChannelsBypassingDnd field, which is a
// state rather than a part of the policy.
- return mConfig.toNotificationPolicy(implicitRule.zenPolicy);
+ return config.toNotificationPolicy(implicitRule.zenPolicy);
} else {
- return getNotificationPolicy();
+ return getNotificationPolicy(user);
}
}
}
@@ -758,24 +801,8 @@ public class ZenModeHelper {
rule.id = implicitRuleId(pkg);
rule.pkg = pkg;
rule.creationTime = mClock.millis();
-
- Binder.withCleanCallingIdentity(() -> {
- try {
- ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
- rule.name = applicationInfo.loadLabel(mPm).toString();
- if (!Flags.modesUi()) {
- rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Should not happen, since it's the app calling us (?)
- Log.w(TAG, "Package not found for creating implicit zen rule");
- rule.name = "Unknown";
- }
- });
-
+ updateImplicitZenRuleNameAndDescription(rule);
rule.type = AutomaticZenRule.TYPE_OTHER;
- rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
- rule.name);
rule.condition = null;
rule.conditionId = new Uri.Builder()
.scheme(Condition.SCHEME)
@@ -790,13 +817,46 @@ public class ZenModeHelper {
return rule;
}
- boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
- int callingUid) {
+ private void updateImplicitZenRuleNameAndDescription(ZenRule rule) {
+ checkArgument(isImplicitRuleId(rule.id));
+ requireNonNull(rule.pkg, "Implicit rule is not associated to package yet!");
+
+ String pkgAppName = Binder.withCleanCallingIdentity(() -> {
+ try {
+ ApplicationInfo applicationInfo = mPm.getApplicationInfo(rule.pkg, 0);
+ return applicationInfo.loadLabel(mPm).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ // Should not happen. When creating it's the app calling us, and when updating
+ // the rule would've been deleted if the package was removed.
+ Slog.e(TAG, "Package not found when updating implicit zen rule name", e);
+ return null;
+ }
+ });
+
+ if (pkgAppName != null) {
+ if ((rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
+ if (Flags.modesUi()) {
+ rule.name = mContext.getString(R.string.zen_mode_implicit_name, pkgAppName);
+ } else {
+ rule.name = pkgAppName;
+ }
+ }
+ rule.triggerDescription = mContext.getString(
+ R.string.zen_mode_implicit_trigger_description, pkgAppName);
+ } else if (rule.name == null) {
+ // We must give a new rule SOME name. But this path should never be hit.
+ rule.name = "Unknown";
+ }
+ }
+
+ boolean removeAutomaticZenRule(UserHandle user, String id, @ConfigOrigin int origin,
+ String reason, int callingUid) {
checkManageRuleOrigin("removeAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return false;
- newConfig = mConfig.copy();
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return false;
+ newConfig = config.copy();
ZenRule ruleToRemove = newConfig.automaticRules.get(id);
if (ruleToRemove == null) return false;
if (canManageAutomaticZenRule(ruleToRemove)) {
@@ -818,18 +878,19 @@ public class ZenModeHelper {
"Cannot delete rules not owned by your condition provider");
}
dispatchOnAutomaticRuleStatusChanged(
- mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
+ config.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
}
}
- boolean removeAutomaticZenRules(String packageName, @ConfigOrigin int origin,
+ boolean removeAutomaticZenRules(UserHandle user, String packageName, @ConfigOrigin int origin,
String reason, int callingUid) {
checkManageRuleOrigin("removeAutomaticZenRules", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return false;
- newConfig = mConfig.copy();
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return false;
+ newConfig = config.copy();
for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) {
@@ -877,12 +938,13 @@ public class ZenModeHelper {
}
@Condition.State
- int getAutomaticZenRuleState(String id) {
+ int getAutomaticZenRuleState(UserHandle user, String id) {
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return Condition.STATE_UNKNOWN;
}
- ZenRule rule = mConfig.automaticRules.get(id);
+ ZenRule rule = config.automaticRules.get(id);
if (rule == null || !canManageAutomaticZenRule(rule)) {
return Condition.STATE_UNKNOWN;
}
@@ -895,14 +957,15 @@ public class ZenModeHelper {
}
}
- void setAutomaticZenRuleState(String id, Condition condition, @ConfigOrigin int origin,
- int callingUid) {
+ void setAutomaticZenRuleState(UserHandle user, String id, Condition condition,
+ @ConfigOrigin int origin, int callingUid) {
checkSetRuleStateOrigin("setAutomaticZenRuleState(String id)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
- newConfig = mConfig.copy();
+ newConfig = config.copy();
ZenRule rule = newConfig.automaticRules.get(id);
if (Flags.modesApi()) {
if (rule != null && canManageAutomaticZenRule(rule)) {
@@ -917,13 +980,14 @@ public class ZenModeHelper {
}
}
- void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
+ void setAutomaticZenRuleState(UserHandle user, Uri ruleDefinition, Condition condition,
@ConfigOrigin int origin, int callingUid) {
checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
- newConfig = mConfig.copy();
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+ newConfig = config.copy();
List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition);
if (Flags.modesApi()) {
@@ -957,7 +1021,13 @@ public class ZenModeHelper {
private static void applyConditionAndReconsiderOverride(ZenRule rule, Condition condition,
int origin) {
if (Flags.modesApi() && Flags.modesUi()) {
- if (origin == ORIGIN_USER_IN_SYSTEMUI && condition != null
+ if (isImplicitRuleId(rule.id)) {
+ // Implicit rules do not use overrides, and always apply conditions directly.
+ // This is compatible with the previous behavior (where the package set the
+ // interruption filter, and no "snoozing" took place if the user changed it later).
+ rule.condition = condition;
+ rule.resetConditionOverride();
+ } else if (origin == ORIGIN_USER_IN_SYSTEMUI && condition != null
&& condition.source == SOURCE_USER_ACTION) {
// Apply as override, instead of actual condition.
// If the new override is the reverse of a previous (still active) override, try
@@ -1017,13 +1087,16 @@ public class ZenModeHelper {
return true;
}
- public int getCurrentInstanceCount(ComponentName cn) {
+ public int getCurrentInstanceCount(UserHandle user, ComponentName cn) {
if (cn == null) {
return 0;
}
int count = 0;
synchronized (mConfigLock) {
- for (ZenRule rule : mConfig.automaticRules.values()) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return 0;
+
+ for (ZenRule rule : config.automaticRules.values()) {
if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) {
count++;
}
@@ -1034,13 +1107,16 @@ public class ZenModeHelper {
// Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
// package rather than a condition provider service or activity.
- private int getPackageRuleCount(String pkg) {
+ private int getPackageRuleCount(UserHandle user, String pkg) {
if (pkg == null) {
return 0;
}
int count = 0;
synchronized (mConfigLock) {
- for (ZenRule rule : mConfig.automaticRules.values()) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return 0;
+
+ for (ZenRule rule : config.automaticRules.values()) {
if (pkg.equals(rule.getPkg())) {
count++;
}
@@ -1073,13 +1149,15 @@ public class ZenModeHelper {
void updateZenRulesOnLocaleChange() {
updateRuleStringsForCurrentLocale(mContext, mDefaultConfig);
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(UserHandle.CURRENT);
+ if (config == null) {
return;
}
- ZenModeConfig config = mConfig.copy();
+
+ ZenModeConfig newConfig = config.copy();
boolean updated = false;
for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
- ZenRule currRule = config.automaticRules.get(defaultRule.id);
+ ZenRule currRule = newConfig.automaticRules.get(defaultRule.id);
// if default rule wasn't user-modified use localized name
// instead of previous system name
if (currRule != null
@@ -1095,14 +1173,16 @@ public class ZenModeHelper {
}
}
if (Flags.modesApi() && Flags.modesUi()) {
- for (ZenRule rule : config.automaticRules.values()) {
+ for (ZenRule rule : newConfig.automaticRules.values()) {
if (SystemZenRules.isSystemOwnedRule(rule)) {
updated |= SystemZenRules.updateTriggerDescription(mContext, rule);
+ } else if (isImplicitRuleId(rule.id)) {
+ updateImplicitZenRuleNameAndDescription(rule);
}
}
}
if (updated) {
- setConfigLocked(config, null, ORIGIN_SYSTEM,
+ setConfigLocked(newConfig, null, ORIGIN_SYSTEM,
"updateZenRulesOnLocaleChange", Process.SYSTEM_UID);
}
}
@@ -1162,8 +1242,8 @@ public class ZenModeHelper {
* deactivated) unless the update has origin == {@link ZenModeConfig#ORIGIN_USER_IN_SYSTEMUI}.
*/
@GuardedBy("mConfigLock")
- private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
- @ConfigOrigin int origin, boolean isNew) {
+ private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenModeConfig config,
+ ZenRule rule, @ConfigOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
boolean modified = false;
// These values can always be edited by the app, so we apply changes immediately.
@@ -1299,7 +1379,7 @@ public class ZenModeHelper {
}
// Updates the bitmask and values for all policy fields, based on the origin.
- modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
+ modified |= updatePolicy(config, rule, azr.getZenPolicy(), updateBitmask, isNew);
// Updates the bitmask and values for all device effect fields, based on the origin.
modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
@@ -1352,13 +1432,13 @@ public class ZenModeHelper {
* <p>Returns {@code true} if the policy of the rule was modified.
*/
@GuardedBy("mConfigLock")
- private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
- boolean updateBitmask, boolean isNew) {
+ private boolean updatePolicy(ZenModeConfig config, ZenRule zenRule,
+ @Nullable ZenPolicy newPolicy, boolean updateBitmask, boolean isNew) {
if (newPolicy == null) {
if (isNew) {
// Newly created rule with no provided policy; fill in with the default.
zenRule.zenPolicy =
- (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy())
+ (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : config.getZenPolicy())
.copy();
return true;
}
@@ -1370,7 +1450,7 @@ public class ZenModeHelper {
// fields in the bitmask should be marked as updated.
ZenPolicy oldPolicy = zenRule.zenPolicy != null
? zenRule.zenPolicy
- : (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy());
+ : (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : config.getZenPolicy());
// If this is updating a rule rather than creating a new one, keep any fields from the
// old policy if they are unspecified in the new policy. For newly created rules, oldPolicy
@@ -1559,6 +1639,31 @@ public class ZenModeHelper {
return azr;
}
+ // Update only the hasPriorityChannels state (aka areChannelsBypassingDnd) without modifying
+ // any of the rest of the existing policy. This allows components that only want to modify
+ // this bit (PreferencesHelper) to not have to adjust the rest of the policy.
+ protected void updateHasPriorityChannels(UserHandle user, boolean hasPriorityChannels) {
+ if (!Flags.modesUi()) {
+ Log.wtf(TAG, "updateHasPriorityChannels called without modes_ui");
+ }
+ synchronized (mConfigLock) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
+ // If it already matches, do nothing
+ if (config.areChannelsBypassingDnd == hasPriorityChannels) {
+ return;
+ }
+
+ ZenModeConfig newConfig = config.copy();
+ newConfig.areChannelsBypassingDnd = hasPriorityChannels;
+ // The updated calculation of whether there are priority channels is always done by
+ // the system, even if the event causing the calculation had a different origin.
+ setConfigLocked(newConfig, null, ORIGIN_SYSTEM, "updateHasPriorityChannels",
+ Process.SYSTEM_UID);
+ }
+ }
+
@SuppressLint("MissingPermission")
void scheduleActivationBroadcast(String pkg, @UserIdInt int userId, String ruleId,
boolean activated) {
@@ -1580,24 +1685,25 @@ public class ZenModeHelper {
: AUTOMATIC_RULE_STATUS_DISABLED);
}
- void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
+ void setManualZenMode(UserHandle user, int zenMode, Uri conditionId, @ConfigOrigin int origin,
String reason, @Nullable String caller, int callingUid) {
- setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
+ setManualZenMode(user, zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
callingUid);
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
}
- private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
- String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
+ private void setManualZenMode(UserHandle user, int zenMode, Uri conditionId,
+ @ConfigOrigin int origin, String reason, @Nullable String caller, boolean setRingerMode,
+ int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
if (!Global.isValidZenMode(zenMode)) return;
if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ " conditionId=" + conditionId + " reason=" + reason
+ " setRingerMode=" + setRingerMode);
- newConfig = mConfig.copy();
+ newConfig = config.copy();
if (Flags.modesUi()) {
newConfig.manualRule.enabler = caller;
newConfig.manualRule.conditionId = conditionId != null ? conditionId : Uri.EMPTY;
@@ -1640,18 +1746,20 @@ public class ZenModeHelper {
}
}
- public void setManualZenRuleDeviceEffects(ZenDeviceEffects deviceEffects,
+ public void setManualZenRuleDeviceEffects(UserHandle user, ZenDeviceEffects deviceEffects,
@ConfigOrigin int origin, String reason, int callingUid) {
if (!Flags.modesUi()) {
return;
}
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
if (DEBUG) Log.d(TAG, "updateManualRule " + deviceEffects
+ " reason=" + reason
+ " callingUid=" + callingUid);
- newConfig = mConfig.copy();
+ newConfig = config.copy();
newConfig.manualRule.pkg = PACKAGE_ANDROID;
newConfig.manualRule.zenDeviceEffects = deviceEffects;
@@ -1681,7 +1789,7 @@ public class ZenModeHelper {
pw.println(Global.zenModeToString(mZenMode));
pw.print(prefix);
pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
final int N = mConfigs.size();
for (int i = 0; i < N; i++) {
dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
@@ -1702,11 +1810,10 @@ public class ZenModeHelper {
pw.println(config);
}
- public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
- throws XmlPullParserException, IOException {
- ZenModeConfig config = ZenModeConfig.readXml(parser);
+ public boolean readXml(TypedXmlPullParser parser, boolean forRestore, int userId,
+ @Nullable BackupRestoreEventLogger logger) throws XmlPullParserException, IOException {
+ ZenModeConfig config = ZenModeConfig.readXml(parser, logger);
String reason = "readXml";
-
if (config != null) {
if (forRestore) {
config.user = userId;
@@ -1783,17 +1890,6 @@ public class ZenModeHelper {
SystemZenRules.maybeUpgradeRules(mContext, config);
}
- // Resolve user id for settings.
- userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
- if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
- } else {
- // devices not restoring/upgrading already have updated zen settings
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
- }
-
if (Flags.modesApi() && forRestore) {
// Note: forBackup doesn't write deletedRules, but just in case.
config.deletedRules.clear();
@@ -1809,22 +1905,38 @@ public class ZenModeHelper {
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
- setConfigLocked(config, null,
+ return setConfigLocked(config, null,
forRestore ? ORIGIN_RESTORE_BACKUP : ORIGIN_INIT, reason,
Process.SYSTEM_UID);
}
}
+ return false;
}
- public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId)
- throws IOException {
- synchronized (mConfigsArrayLock) {
+ public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId,
+ @Nullable BackupRestoreEventLogger logger) throws IOException {
+ synchronized (mConfigLock) {
+ int successfulWrites = 0;
+ int unsuccessfulWrites = 0;
final int n = mConfigs.size();
for (int i = 0; i < n; i++) {
if (forBackup && mConfigs.keyAt(i) != userId) {
continue;
}
- mConfigs.valueAt(i).writeXml(out, version, forBackup);
+ try {
+ mConfigs.valueAt(i).writeXml(out, version, forBackup, logger);
+ successfulWrites++;
+ } catch (Exception e) {
+ Slog.e(TAG, "failed to write config", e);
+ unsuccessfulWrites++;
+ }
+ }
+ if (logger != null) {
+ logger.logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, successfulWrites);
+ if (unsuccessfulWrites > 0) {
+ logger.logItemsBackupFailed(DATA_TYPE_ZEN_CONFIG,
+ unsuccessfulWrites, ERROR_XML_PARSING);
+ }
}
}
}
@@ -1832,24 +1944,39 @@ public class ZenModeHelper {
/**
* @return user-specified default notification policy for priority only do not disturb
*/
- public Policy getNotificationPolicy() {
+ @Nullable
+ public Policy getNotificationPolicy(UserHandle user) {
synchronized (mConfigLock) {
- return getNotificationPolicy(mConfig);
+ if (Flags.modesMultiuser()) {
+ // Return a fallback (default) policy for users without a zen config.
+ // Note that zen updates (setPolicy, setFilter) won't be applied, so this is mostly
+ // about preventing NPEs for careless callers.
+ ZenModeConfig config = getConfigLocked(user);
+ return config != null
+ ? getNotificationPolicy(config)
+ : getNotificationPolicy(mDefaultConfig);
+ } else {
+ return getNotificationPolicy(getConfigLocked(user));
+ }
}
}
- private static Policy getNotificationPolicy(ZenModeConfig config) {
+ @Nullable
+ private static Policy getNotificationPolicy(@Nullable ZenModeConfig config) {
return config == null ? null : config.toNotificationPolicy();
}
/**
* Sets the global notification policy used for priority only do not disturb
*/
- public void setNotificationPolicy(Policy policy, @ConfigOrigin int origin,
+ public void setNotificationPolicy(UserHandle user, Policy policy, @ConfigOrigin int origin,
int callingUid) {
synchronized (mConfigLock) {
- if (policy == null || mConfig == null) return;
- final ZenModeConfig newConfig = mConfig.copy();
+ if (policy == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
+ final ZenModeConfig newConfig = config.copy();
if (Flags.modesApi() && !Flags.modesUi()) {
// Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
// the user cannot edit zen policy to emulate the previous "inheritance".
@@ -1877,7 +2004,7 @@ public class ZenModeHelper {
}
/**
- * Cleans up obsolete rules:
+ * Cleans up obsolete rules in the current {@link ZenModeConfig}.
* <ul>
* <li>Rule instances whose owner is not installed.
* <li>Deleted rules that were deleted more than 30 days ago.
@@ -1949,6 +2076,27 @@ public class ZenModeHelper {
return mDefaultConfig.getZenPolicy();
}
+ /**
+ * Returns the {@link ZenModeConfig} corresponding to the supplied {@link UserHandle}.
+ * The result will be {@link #mConfig} if the user is {@link UserHandle#CURRENT}, or the same
+ * as {@link #mUser}, otherwise will be the corresponding entry in {@link #mConfigs}.
+ *
+ * <p>Remember to continue holding {@link #mConfigLock} while operating on the returned value.
+ */
+ @Nullable
+ @GuardedBy("mConfigLock")
+ private ZenModeConfig getConfigLocked(@NonNull UserHandle user) {
+ if (Flags.modesMultiuser()) {
+ if (user.getIdentifier() == UserHandle.USER_CURRENT || user.getIdentifier() == mUser) {
+ return mConfig;
+ } else {
+ return mConfigs.get(user.getIdentifier());
+ }
+ } else {
+ return mConfig;
+ }
+ }
+
@GuardedBy("mConfigLock")
private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
@ConfigOrigin int origin, String reason, int callingUid) {
@@ -1975,7 +2123,7 @@ public class ZenModeHelper {
}
if (config.user != mUser) {
// simply store away for background users
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
mConfigs.put(config.user, config);
}
if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
@@ -1984,7 +2132,7 @@ public class ZenModeHelper {
// handle CPS backed conditions - danger! may modify config
mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
mConfigs.put(config.user, config);
}
if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
@@ -2062,7 +2210,6 @@ public class ZenModeHelper {
Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
"updated setting");
- showZenUpgradeNotification(zen);
}
private int getPreviousRingerModeSetting() {
@@ -2117,12 +2264,6 @@ public class ZenModeHelper {
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isActive()) {
if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
- // automatic rule triggered dnd and user hasn't seen update dnd dialog
- if (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, 1) == 0) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 1);
- }
zen = automaticRule.zenMode;
}
}
@@ -2132,7 +2273,8 @@ public class ZenModeHelper {
}
@GuardedBy("mConfigLock")
- private void applyCustomPolicy(ZenPolicy policy, ZenRule rule, boolean useManualConfig) {
+ private void applyCustomPolicy(ZenModeConfig config, ZenPolicy policy, ZenRule rule,
+ boolean useManualConfig) {
if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
if (Flags.modesApi() && Flags.modesUi()) {
policy.apply(ZenPolicy.getBasePolicyInterruptionFilterNone());
@@ -2158,8 +2300,8 @@ public class ZenModeHelper {
} else {
if (Flags.modesApi()) {
if (useManualConfig) {
- // manual rule is configured using the settings stored directly in mConfig
- policy.apply(mConfig.getZenPolicy());
+ // manual rule is configured using the settings stored directly in ZenModeConfig
+ policy.apply(config.getZenPolicy());
} else {
// under modes_api flag, an active automatic rule with no specified policy
// inherits the device default settings as stored in mDefaultConfig. While the
@@ -2167,11 +2309,11 @@ public class ZenModeHelper {
// catch any that may have fallen through the cracks.
Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
policy.apply(Flags.modesUi()
- ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy());
+ ? mDefaultConfig.getZenPolicy() : config.getZenPolicy());
}
} else {
// active rule with no specified policy inherits the manual rule config settings
- policy.apply(mConfig.getZenPolicy());
+ policy.apply(config.getZenPolicy());
}
}
}
@@ -2184,7 +2326,7 @@ public class ZenModeHelper {
ZenPolicy policy = new ZenPolicy();
ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
if (mConfig.isManualActive()) {
- applyCustomPolicy(policy, mConfig.manualRule, true);
+ applyCustomPolicy(mConfig, policy, mConfig.manualRule, true);
if (Flags.modesApi()) {
deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
}
@@ -2196,7 +2338,7 @@ public class ZenModeHelper {
// policy. This is relevant in case some other active rule has a more
// restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) {
- applyCustomPolicy(policy, automaticRule, false);
+ applyCustomPolicy(mConfig, policy, automaticRule, false);
}
if (Flags.modesApi()) {
deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
@@ -2311,7 +2453,7 @@ public class ZenModeHelper {
|| (mSuppressedEffects & SUPPRESSED_EFFECT_NOTIFICATIONS) != 0;
// call restrictions
final boolean muteCalls = zenAlarmsOnly
- || (zenPriorityOnly && !(allowCalls || allowRepeatCallers))
+ || (zenPriorityOnly && (!allowCalls || !allowRepeatCallers))
|| (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
// alarm restrictions
final boolean muteAlarms = zenPriorityOnly && !allowAlarms;
@@ -2435,7 +2577,8 @@ public class ZenModeHelper {
try {
parser = resources.getXml(R.xml.default_zen_mode_config);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(XmlUtils.makeTyped(parser));
+ final ZenModeConfig config =
+ ZenModeConfig.readXml(XmlUtils.makeTyped(parser), null);
if (config != null) return config;
}
} catch (Exception e) {
@@ -2459,7 +2602,7 @@ public class ZenModeHelper {
* Generate pulled atoms about do not disturb configurations.
*/
public void pullRules(List<StatsEvent> events) {
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
final int numConfigs = mConfigs.size();
for (int i = 0; i < numConfigs; i++) {
final int user = mConfigs.keyAt(i);
@@ -2486,7 +2629,7 @@ public class ZenModeHelper {
}
}
- @GuardedBy("mConfigsArrayLock")
+ @GuardedBy("mConfigLock")
private void ruleToProtoLocked(int user, ZenRule rule, boolean isManualRule,
List<StatsEvent> events) {
// Make the ID safe.
@@ -2591,7 +2734,7 @@ public class ZenModeHelper {
}
if (newZen != -1) {
- setManualZenMode(newZen, null, ORIGIN_SYSTEM,
+ setManualZenMode(UserHandle.CURRENT, newZen, null, ORIGIN_SYSTEM,
"ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
Process.SYSTEM_UID);
}
@@ -2636,7 +2779,7 @@ public class ZenModeHelper {
break;
}
if (newZen != -1) {
- setManualZenMode(newZen, null, ORIGIN_SYSTEM,
+ setManualZenMode(UserHandle.CURRENT, newZen, null, ORIGIN_SYSTEM,
"ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
}
@@ -2702,62 +2845,6 @@ public class ZenModeHelper {
}
}
- private void showZenUpgradeNotification(int zen) {
- final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- final boolean showNotification = mIsSystemServicesReady
- && zen != Global.ZEN_MODE_OFF
- && !isWatch
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
-
- if (isWatch) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
-
- if (showNotification) {
- mNotificationManager.notify(TAG, SystemMessage.NOTE_ZEN_UPGRADE,
- createZenUpgradeNotification());
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
- }
-
- @VisibleForTesting
- protected Notification createZenUpgradeNotification() {
- final Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- mContext.getResources().getString(R.string.global_action_settings));
- int title = R.string.zen_upgrade_notification_title;
- int content = R.string.zen_upgrade_notification_content;
- int drawable = R.drawable.ic_zen_24dp;
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(
- getConsolidatedNotificationPolicy().suppressedVisualEffects)) {
- title = R.string.zen_upgrade_notification_visd_title;
- content = R.string.zen_upgrade_notification_visd_content;
- drawable = R.drawable.ic_dnd_block_notifications;
- }
-
- Intent onboardingIntent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return new Notification.Builder(mContext, SystemNotificationChannels.DO_NOT_DISTURB)
- .setAutoCancel(true)
- .setSmallIcon(R.drawable.ic_settings_24dp)
- .setLargeIcon(Icon.createWithResource(mContext, drawable))
- .setContentTitle(mContext.getResources().getString(title))
- .setContentText(mContext.getResources().getString(content))
- .setContentIntent(PendingIntent.getActivity(mContext, 0, onboardingIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
- .setAutoCancel(true)
- .setLocalOnly(true)
- .addExtras(extras)
- .setStyle(new Notification.BigTextStyle())
- .build();
- }
-
private int drawableResNameToResId(String packageName, String resourceName) {
if (TextUtils.isEmpty(resourceName)) {
return 0;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index be3adc142fa4..65a38ae1fcde 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -123,13 +123,6 @@ flag {
}
flag {
- name: "use_ipcdatacache_channels"
- namespace: "systemui"
- description: "Adds an IPCDataCache for notification channel/group lookups"
- bug: "331677193"
-}
-
-flag {
name: "use_ssm_user_switch_signal"
namespace: "systemui"
description: "This flag controls which signal is used to handle a user switch system event"
@@ -144,6 +137,13 @@ flag {
}
flag {
+ name: "notification_minimalism"
+ namespace: "systemui"
+ description: "Minimize the notifications to show on the lockscreen."
+ bug: "330387368"
+}
+
+flag {
name: "notification_force_group_singletons"
namespace: "systemui"
description: "This flag enables forced auto-grouping singleton groups"
@@ -158,8 +158,32 @@ flag {
}
flag {
+ name: "notification_lock_screen_settings"
+ namespace: "systemui"
+ description: "This flag enables the new settings page for the notifications on lock screen."
+ bug: "367455695"
+}
+
+flag {
name: "notification_vibration_in_sound_uri"
namespace: "systemui"
description: "This flag enables sound uri with vibration source"
bug: "358524009"
}
+
+flag {
+ name: "notification_verify_channel_sound_uri"
+ namespace: "systemui"
+ description: "Verify Uri permission for sound when creating a notification channel"
+ bug: "337775777"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "notification_vibration_in_sound_uri_for_channel"
+ namespace: "systemui"
+ description: "Enables sound uri with vibration source in notification channel"
+ bug: "351975435"
+}
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 015b7fd74211..38f39393a025 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,6 +19,7 @@ package com.android.server.om;
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
+import android.content.res.Flags;
import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
@@ -162,11 +163,15 @@ public class OverlayActorEnforcer {
return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE;
}
- if (targetOverlayable == null) {
+ // Framework doesn't have <overlayable> declaration by design, and we still want to be able
+ // to enable its overlays from the packages with the permission.
+ if (targetOverlayable == null
+ && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals(
+ "android"))) {
return ActorState.MISSING_OVERLAYABLE;
}
- String actor = targetOverlayable.actor;
+ final String actor = targetOverlayable == null ? null : targetOverlayable.actor;
if (TextUtils.isEmpty(actor)) {
// If there's no actor defined, fallback to the legacy permission check
try {
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
deleted file mode 100644
index 7dd8f2fdcecb..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence;
-
-import static android.system.OsConstants.F_GETFL;
-import static android.system.OsConstants.O_ACCMODE;
-import static android.system.OsConstants.O_RDONLY;
-import static android.system.OsConstants.PROT_READ;
-
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ResponseParams;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
-import android.app.ondeviceintelligence.TokenInfo;
-import android.database.CursorWindow;
-import android.graphics.Bitmap;
-import android.os.BadParcelableException;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SharedMemory;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
- * some known types.
- */
-public class BundleUtil {
- private static final String TAG = "BundleUtil";
-
- /**
- * Validation of the inference request payload as described in {@link InferenceParams}
- * description.
- *
- * @throws BadParcelableException when the bundle does not meet the read-only requirements.
- */
- public static void sanitizeInferenceParams(
- @InferenceParams Bundle bundle) {
- ensureValidBundle(bundle);
-
- if (!bundle.hasFileDescriptors()) {
- return; //safe to exit if there are no FDs and Binders
- }
-
- for (String key : bundle.keySet()) {
- Object obj = bundle.get(key);
- if (obj == null) {
- /* Null value here could also mean deserializing a custom parcelable has failed,
- * and since {@link Bundle} is marked as defusable in system-server - the
- * {@link ClassNotFoundException} exception is swallowed and `null` is returned
- * instead. We want to ensure cleanup of null entries in such case.
- */
- bundle.putObject(key, null);
- continue;
- }
- if (canMarshall(obj) || obj instanceof CursorWindow) {
- continue;
- }
- if (obj instanceof Bundle) {
- sanitizeInferenceParams((Bundle) obj);
- } else if (obj instanceof ParcelFileDescriptor) {
- validatePfdReadOnly((ParcelFileDescriptor) obj);
- } else if (obj instanceof SharedMemory) {
- ((SharedMemory) obj).setProtect(PROT_READ);
- } else if (obj instanceof Bitmap) {
- validateBitmap((Bitmap) obj);
- } else if (obj instanceof Parcelable[]) {
- validateParcelableArray((Parcelable[]) obj);
- } else {
- throw new BadParcelableException(
- "Unsupported Parcelable type encountered in the Bundle: "
- + obj.getClass().getSimpleName());
- }
- }
- }
-
- /**
- * Validation of the inference request payload as described in {@link ResponseParams}
- * description.
- *
- * @throws BadParcelableException when the bundle does not meet the read-only requirements.
- */
- public static void sanitizeResponseParams(
- @ResponseParams Bundle bundle) {
- ensureValidBundle(bundle);
-
- if (!bundle.hasFileDescriptors()) {
- return; //safe to exit if there are no FDs and Binders
- }
-
- for (String key : bundle.keySet()) {
- Object obj = bundle.get(key);
- if (obj == null) {
- /* Null value here could also mean deserializing a custom parcelable has failed,
- * and since {@link Bundle} is marked as defusable in system-server - the
- * {@link ClassNotFoundException} exception is swallowed and `null` is returned
- * instead. We want to ensure cleanup of null entries in such case.
- */
- bundle.putObject(key, null);
- continue;
- }
- if (canMarshall(obj)) {
- continue;
- }
-
- if (obj instanceof Bundle) {
- sanitizeResponseParams((Bundle) obj);
- } else if (obj instanceof ParcelFileDescriptor) {
- validatePfdReadOnly((ParcelFileDescriptor) obj);
- } else if (obj instanceof Bitmap) {
- validateBitmap((Bitmap) obj);
- } else if (obj instanceof Parcelable[]) {
- validateParcelableArray((Parcelable[]) obj);
- } else {
- throw new BadParcelableException(
- "Unsupported Parcelable type encountered in the Bundle: "
- + obj.getClass().getSimpleName());
- }
- }
- }
-
- /**
- * Validation of the inference request payload as described in {@link StateParams}
- * description.
- *
- * @throws BadParcelableException when the bundle does not meet the read-only requirements.
- */
- public static void sanitizeStateParams(
- @StateParams Bundle bundle) {
- ensureValidBundle(bundle);
-
- if (!bundle.hasFileDescriptors()) {
- return; //safe to exit if there are no FDs and Binders
- }
-
- for (String key : bundle.keySet()) {
- Object obj = bundle.get(key);
- if (obj == null) {
- /* Null value here could also mean deserializing a custom parcelable has failed,
- * and since {@link Bundle} is marked as defusable in system-server - the
- * {@link ClassNotFoundException} exception is swallowed and `null` is returned
- * instead. We want to ensure cleanup of null entries in such case.
- */
- bundle.putObject(key, null);
- continue;
- }
- if (canMarshall(obj)) {
- continue;
- }
-
- if (obj instanceof ParcelFileDescriptor) {
- validatePfdReadOnly((ParcelFileDescriptor) obj);
- } else {
- throw new BadParcelableException(
- "Unsupported Parcelable type encountered in the Bundle: "
- + obj.getClass().getSimpleName());
- }
- }
- }
-
-
- public static IStreamingResponseCallback wrapWithValidation(
- IStreamingResponseCallback streamingResponseCallback,
- Executor resourceClosingExecutor,
- AndroidFuture future,
- InferenceInfoStore inferenceInfoStore) {
- return new IStreamingResponseCallback.Stub() {
- @Override
- public void onNewContent(Bundle processedResult) throws RemoteException {
- try {
- sanitizeResponseParams(processedResult);
- streamingResponseCallback.onNewContent(processedResult);
- } finally {
- resourceClosingExecutor.execute(() -> tryCloseResource(processedResult));
- }
- }
-
- @Override
- public void onSuccess(Bundle resultBundle)
- throws RemoteException {
- try {
- sanitizeResponseParams(resultBundle);
- streamingResponseCallback.onSuccess(resultBundle);
- } finally {
- inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
- resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
- future.complete(null);
- }
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage,
- PersistableBundle errorParams) throws RemoteException {
- streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
- inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
- future.completeExceptionally(new TimeoutException());
- }
-
- @Override
- public void onDataAugmentRequest(Bundle processedContent,
- RemoteCallback remoteCallback)
- throws RemoteException {
- try {
- sanitizeResponseParams(processedContent);
- streamingResponseCallback.onDataAugmentRequest(processedContent,
- new RemoteCallback(
- augmentedData -> {
- try {
- sanitizeInferenceParams(augmentedData);
- remoteCallback.sendResult(augmentedData);
- } finally {
- resourceClosingExecutor.execute(
- () -> tryCloseResource(augmentedData));
- }
- }));
- } finally {
- resourceClosingExecutor.execute(() -> tryCloseResource(processedContent));
- }
- }
- };
- }
-
- public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
- Executor resourceClosingExecutor,
- AndroidFuture future,
- InferenceInfoStore inferenceInfoStore) {
- return new IResponseCallback.Stub() {
- @Override
- public void onSuccess(Bundle resultBundle)
- throws RemoteException {
- try {
- sanitizeResponseParams(resultBundle);
- responseCallback.onSuccess(resultBundle);
- } finally {
- inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
- resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
- future.complete(null);
- }
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage,
- PersistableBundle errorParams) throws RemoteException {
- responseCallback.onFailure(errorCode, errorMessage, errorParams);
- inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
- future.completeExceptionally(new TimeoutException());
- }
-
- @Override
- public void onDataAugmentRequest(Bundle processedContent,
- RemoteCallback remoteCallback)
- throws RemoteException {
- try {
- sanitizeResponseParams(processedContent);
- responseCallback.onDataAugmentRequest(processedContent, new RemoteCallback(
- augmentedData -> {
- try {
- sanitizeInferenceParams(augmentedData);
- remoteCallback.sendResult(augmentedData);
- } finally {
- resourceClosingExecutor.execute(
- () -> tryCloseResource(augmentedData));
- }
- }));
- } finally {
- resourceClosingExecutor.execute(() -> tryCloseResource(processedContent));
- }
- }
- };
- }
-
-
- public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
- AndroidFuture future,
- InferenceInfoStore inferenceInfoStore) {
- return new ITokenInfoCallback.Stub() {
- @Override
- public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
- responseCallback.onSuccess(tokenInfo);
- inferenceInfoStore.addInferenceInfoFromBundle(tokenInfo.getInfoParams());
- future.complete(null);
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
- throws RemoteException {
- responseCallback.onFailure(errorCode, errorMessage, errorParams);
- inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
- future.completeExceptionally(new TimeoutException());
- }
- };
- }
-
- private static boolean canMarshall(Object obj) {
- return obj instanceof byte[] || obj instanceof PersistableBundle
- || PersistableBundle.isValidType(obj);
- }
-
- private static void ensureValidBundle(Bundle bundle) {
- if (bundle == null) {
- throw new IllegalArgumentException("Request passed is expected to be non-null");
- }
-
- if (bundle.hasBinders() != Bundle.STATUS_BINDERS_NOT_PRESENT) {
- throw new BadParcelableException("Bundle should not contain IBinder objects.");
- }
- }
-
- private static void validateParcelableArray(Parcelable[] parcelables) {
- if (parcelables.length > 0
- && parcelables[0] instanceof ParcelFileDescriptor) {
- // Safe to cast
- validatePfdsReadOnly(parcelables);
- } else if (parcelables.length > 0
- && parcelables[0] instanceof Bitmap) {
- validateBitmapsImmutable(parcelables);
- } else {
- throw new BadParcelableException(
- "Could not cast to any known parcelable array");
- }
- }
-
- public static void validatePfdsReadOnly(Parcelable[] pfds) {
- for (Parcelable pfd : pfds) {
- validatePfdReadOnly((ParcelFileDescriptor) pfd);
- }
- }
-
- public static void validatePfdReadOnly(ParcelFileDescriptor pfd) {
- if (pfd == null) {
- return;
- }
- try {
- int readMode = Os.fcntlInt(pfd.getFileDescriptor(), F_GETFL, 0) & O_ACCMODE;
- if (readMode != O_RDONLY) {
- throw new BadParcelableException(
- "Bundle contains a parcel file descriptor which is not read-only.");
- }
- } catch (ErrnoException e) {
- throw new BadParcelableException(
- "Invalid File descriptor passed in the Bundle.", e);
- }
- }
-
- private static void validateBitmap(Bitmap obj) {
- if (obj.isMutable()) {
- throw new BadParcelableException(
- "Encountered a mutable Bitmap in the Bundle at key : " + obj);
- }
- }
-
- private static void validateBitmapsImmutable(Parcelable[] bitmaps) {
- for (Parcelable bitmap : bitmaps) {
- validateBitmap((Bitmap) bitmap);
- }
- }
-
- public static void tryCloseResource(Bundle bundle) {
- if (bundle == null || bundle.isEmpty() || !bundle.hasFileDescriptors()) {
- return;
- }
-
- for (String key : bundle.keySet()) {
- Object obj = bundle.get(key);
-
- try {
- // TODO(b/329898589) : This can be cleaned up after the flag passing is fixed.
- if (obj instanceof ParcelFileDescriptor) {
- ((ParcelFileDescriptor) obj).close();
- } else if (obj instanceof CursorWindow) {
- ((CursorWindow) obj).close();
- } else if (obj instanceof SharedMemory) {
- // TODO(b/331796886) : Shared memory should honour parcelable flags.
- ((SharedMemory) obj).close();
- }
- } catch (Exception e) {
- Log.e(TAG, "Error closing resource with key: " + key, e);
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
deleted file mode 100644
index b532d5a8f8fc..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence;
-
-import android.app.ondeviceintelligence.InferenceInfo;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
-import android.util.Slog;
-import android.util.Base64;
-
-import java.io.IOException;
-import java.util.Comparator;
-import java.util.List;
-import java.util.TreeSet;
-
-public class InferenceInfoStore {
- private static final String TAG = "InferenceInfoStore";
- private final TreeSet<InferenceInfo> inferenceInfos;
- private final long maxAgeMs;
-
- public InferenceInfoStore(long maxAgeMs) {
- this.maxAgeMs = maxAgeMs;
- this.inferenceInfos = new TreeSet<>(
- Comparator.comparingLong(InferenceInfo::getStartTimeMs));
- }
-
- public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
- return inferenceInfos.stream().filter(
- info -> info.getStartTimeMs() > startTimeEpochMillis).toList();
- }
-
- public void addInferenceInfoFromBundle(PersistableBundle pb) {
- if (!pb.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
- return;
- }
-
- try {
- String infoBytesBase64String = pb.getString(
- OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
- if (infoBytesBase64String != null) {
- byte[] infoBytes = Base64.decode(infoBytesBase64String, Base64.DEFAULT);
- com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
- com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
- infoBytes);
- add(inferenceInfo);
- }
- } catch (IOException e) {
- Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
- }
- }
-
- public void addInferenceInfoFromBundle(Bundle b) {
- if (!b.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
- return;
- }
-
- try {
- byte[] infoBytes = b.getByteArray(
- OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
- if (infoBytes != null) {
- com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
- com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
- infoBytes);
- add(inferenceInfo);
- }
- } catch (IOException e) {
- Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
- }
- }
-
- private synchronized void add(com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
- while (!inferenceInfos.isEmpty()
- && System.currentTimeMillis() - inferenceInfos.first().getStartTimeMs()
- > maxAgeMs) {
- inferenceInfos.pollFirst();
- }
- inferenceInfos.add(toInferenceInfo(info));
- }
-
- private static InferenceInfo toInferenceInfo(
- com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
- return new InferenceInfo.Builder().setUid(info.uid).setStartTimeMs(
- info.startTimeMs).setEndTimeMs(info.endTimeMs).setSuspendedTimeMs(
- info.suspendedTimeMs).build();
- }
-} \ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OWNERS b/services/core/java/com/android/server/ondeviceintelligence/OWNERS
deleted file mode 100644
index 09774f78d712..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
deleted file mode 100644
index f6d9dc29d330..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ /dev/null
@@ -1,1106 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence;
-
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
-
-import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
-import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
-import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
-import static com.android.server.ondeviceintelligence.BundleUtil.wrapWithValidation;
-
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.app.AppGlobals;
-import android.app.ondeviceintelligence.DownloadCallback;
-import android.app.ondeviceintelligence.Feature;
-import android.app.ondeviceintelligence.FeatureDetails;
-import android.app.ondeviceintelligence.IDownloadCallback;
-import android.app.ondeviceintelligence.IFeatureCallback;
-import android.app.ondeviceintelligence.IFeatureDetailsCallback;
-import android.app.ondeviceintelligence.IListFeaturesCallback;
-import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
-import android.app.ondeviceintelligence.IProcessingSignal;
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.InferenceInfo;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
-import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
-import android.service.ondeviceintelligence.IRemoteProcessingService;
-import android.service.ondeviceintelligence.IRemoteStorageService;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
-import com.android.internal.os.BackgroundThread;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This is the system service for handling calls on the
- * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
- * service holds connection references to the underlying remote services i.e. the isolated service
- * {@link OnDeviceSandboxedInferenceService} and a regular
- * service counter part {@link OnDeviceIntelligenceService}.
- *
- * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
- * the Inference service for each user, due to possible high memory footprint.
- *
- * @hide
- */
-public class OnDeviceIntelligenceManagerService extends SystemService {
-
- private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
- private static final String KEY_SERVICE_ENABLED = "service_enabled";
-
- /** Handler message to {@link #resetTemporaryServices()} */
- private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
- /** Handler message to clean up temporary broadcast keys. */
- private static final int MSG_RESET_BROADCAST_KEYS = 1;
- /** Handler message to clean up temporary config namespace. */
- private static final int MSG_RESET_CONFIG_NAMESPACE = 2;
-
- /** Default value in absence of {@link DeviceConfig} override. */
- private static final boolean DEFAULT_SERVICE_ENABLED = true;
- private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
-
- private static final String SYSTEM_PACKAGE = "android";
- private static final long MAX_AGE_MS = TimeUnit.HOURS.toMillis(3);
-
-
- private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
- private final Executor callbackExecutor = Executors.newCachedThreadPool();
- private final Executor broadcastExecutor = Executors.newCachedThreadPool();
- private final Executor mConfigExecutor = Executors.newCachedThreadPool();
-
-
- private final Context mContext;
- protected final Object mLock = new Object();
-
- private final InferenceInfoStore mInferenceInfoStore;
- private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
- private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
- volatile boolean mIsServiceEnabled;
-
- @GuardedBy("mLock")
- private int remoteInferenceServiceUid = -1;
-
- @GuardedBy("mLock")
- private String[] mTemporaryServiceNames;
- @GuardedBy("mLock")
- private String[] mTemporaryBroadcastKeys;
- @GuardedBy("mLock")
- private String mBroadcastPackageName;
- @GuardedBy("mLock")
- private String mTemporaryConfigNamespace;
-
- private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
- this::sendUpdatedConfig;
-
-
- /**
- * Handler used to reset the temporary service names.
- */
- private Handler mTemporaryHandler;
- private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
-
-
- public OnDeviceIntelligenceManagerService(Context context) {
- super(context);
- mContext = context;
- mTemporaryServiceNames = new String[0];
- mInferenceInfoStore = new InferenceInfoStore(MAX_AGE_MS);
- }
-
- @Override
- public void onStart() {
- publishBinderService(
- Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
- /* allowIsolated = */true);
- LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
- this::getRemoteInferenceServiceUid);
- }
-
- @Override
- public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_ON_DEVICE_INTELLIGENCE,
- BackgroundThread.getExecutor(),
- (properties) -> onDeviceConfigChange(properties.getKeyset()));
-
- mIsServiceEnabled = isServiceEnabled();
- }
-
- //connect to remote services(if available) during boot phase.
- if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
- try {
- ensureRemoteInferenceServiceInitialized();
- ensureRemoteIntelligenceServiceInitialized();
- } catch (Exception e) {
- Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e);
- }
- }
- }
-
- private void onDeviceConfigChange(@NonNull Set<String> keys) {
- if (keys.contains(KEY_SERVICE_ENABLED)) {
- mIsServiceEnabled = isServiceEnabled();
- }
- }
-
- private boolean isServiceEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_ON_DEVICE_INTELLIGENCE,
- KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
- }
-
- private IBinder getOnDeviceIntelligenceManagerService() {
- return new IOnDeviceIntelligenceManager.Stub() {
- @Override
- public String getRemoteServicePackageName() {
- return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
- }
-
- @Override
- public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
- mContext.enforceCallingPermission(
- Manifest.permission.DUMP, TAG);
- return OnDeviceIntelligenceManagerService.this.getLatestInferenceInfo(
- startTimeEpochMillis);
- }
-
- @Override
- public void getVersion(RemoteCallback remoteCallback) {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
- Objects.requireNonNull(remoteCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- remoteCallback.sendResult(null);
- return;
- }
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.getVersion(new RemoteCallback(
- result -> {
- remoteCallback.sendResult(result);
- future.complete(null);
- }));
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- }
-
- @Override
- public void getFeature(int id, IFeatureCallback featureCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(featureCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureCallback.onFailure(
- OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- return;
- }
- ensureRemoteIntelligenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.getFeature(callerUid, id, new IFeatureCallback.Stub() {
- @Override
- public void onSuccess(Feature result) throws RemoteException {
- featureCallback.onSuccess(result);
- future.complete(null);
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage,
- PersistableBundle errorParams) throws RemoteException {
- featureCallback.onFailure(errorCode, errorMessage, errorParams);
- future.completeExceptionally(new TimeoutException());
- }
- });
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- }
-
- @Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(listFeaturesCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- listFeaturesCallback.onFailure(
- OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- return;
- }
- ensureRemoteIntelligenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.listFeatures(callerUid,
- new IListFeaturesCallback.Stub() {
- @Override
- public void onSuccess(List<Feature> result)
- throws RemoteException {
- listFeaturesCallback.onSuccess(result);
- future.complete(null);
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage,
- PersistableBundle errorParams)
- throws RemoteException {
- listFeaturesCallback.onFailure(errorCode, errorMessage,
- errorParams);
- future.completeExceptionally(new TimeoutException());
- }
- });
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- }
-
- @Override
- public void getFeatureDetails(Feature feature,
- IFeatureDetailsCallback featureDetailsCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(featureDetailsCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureDetailsCallback.onFailure(
- OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- return;
- }
- ensureRemoteIntelligenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.getFeatureDetails(callerUid, feature,
- new IFeatureDetailsCallback.Stub() {
- @Override
- public void onSuccess(FeatureDetails result)
- throws RemoteException {
- future.complete(null);
- featureDetailsCallback.onSuccess(result);
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage,
- PersistableBundle errorParams)
- throws RemoteException {
- future.completeExceptionally(null);
- featureDetailsCallback.onFailure(errorCode,
- errorMessage, errorParams);
- }
- });
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- }
-
- @Override
- public void requestFeatureDownload(Feature feature,
- AndroidFuture cancellationSignalFuture,
- IDownloadCallback downloadCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(downloadCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- downloadCallback.onDownloadFailed(
- DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- }
- ensureRemoteIntelligenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- ListenableDownloadCallback listenableDownloadCallback =
- new ListenableDownloadCallback(
- downloadCallback,
- mMainHandler, future, getIdleTimeoutMs());
- service.requestFeatureDownload(callerUid, feature,
- wrapCancellationFuture(cancellationSignalFuture),
- listenableDownloadCallback);
- return future; // this future has no timeout because, actual download
- // might take long, but we fail early if there is no progress callbacks.
- }
- );
- }
-
-
- @Override
- public void requestTokenInfo(Feature feature,
- Bundle request,
- AndroidFuture cancellationSignalFuture,
- ITokenInfoCallback tokenInfoCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestTokenInfo");
- AndroidFuture<Void> result = null;
- try {
- Objects.requireNonNull(feature);
- sanitizeInferenceParams(request);
- Objects.requireNonNull(tokenInfoCallback);
-
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- tokenInfoCallback.onFailure(
- OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- }
- ensureRemoteInferenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- result = mRemoteInferenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.requestTokenInfo(callerUid, feature,
- request,
- wrapCancellationFuture(cancellationSignalFuture),
- wrapWithValidation(tokenInfoCallback, future,
- mInferenceInfoStore));
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
- resourceClosingExecutor);
- } finally {
- if (result == null) {
- resourceClosingExecutor.execute(() -> BundleUtil.tryCloseResource(request));
- }
- }
- }
-
- @Override
- public void processRequest(Feature feature,
- Bundle request,
- int requestType,
- AndroidFuture cancellationSignalFuture,
- AndroidFuture processingSignalFuture,
- IResponseCallback responseCallback)
- throws RemoteException {
- AndroidFuture<Void> result = null;
- try {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
- Objects.requireNonNull(feature);
- sanitizeInferenceParams(request);
- Objects.requireNonNull(responseCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- responseCallback.onFailure(
- OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- }
- ensureRemoteInferenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- result = mRemoteInferenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.processRequest(callerUid, feature,
- request,
- requestType,
- wrapCancellationFuture(cancellationSignalFuture),
- wrapProcessingFuture(processingSignalFuture),
- wrapWithValidation(responseCallback,
- resourceClosingExecutor, future,
- mInferenceInfoStore));
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
- resourceClosingExecutor);
- } finally {
- if (result == null) {
- resourceClosingExecutor.execute(() -> BundleUtil.tryCloseResource(request));
- }
- }
- }
-
- @Override
- public void processRequestStreaming(Feature feature,
- Bundle request,
- int requestType,
- AndroidFuture cancellationSignalFuture,
- AndroidFuture processingSignalFuture,
- IStreamingResponseCallback streamingCallback) throws RemoteException {
- AndroidFuture<Void> result = null;
- try {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
- Objects.requireNonNull(feature);
- sanitizeInferenceParams(request);
- Objects.requireNonNull(streamingCallback);
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- streamingCallback.onFailure(
- OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- PersistableBundle.EMPTY);
- }
- ensureRemoteInferenceServiceInitialized();
- int callerUid = Binder.getCallingUid();
- result = mRemoteInferenceService.postAsync(
- service -> {
- AndroidFuture future = new AndroidFuture();
- service.processRequestStreaming(callerUid,
- feature,
- request, requestType,
- wrapCancellationFuture(cancellationSignalFuture),
- wrapProcessingFuture(processingSignalFuture),
- wrapWithValidation(streamingCallback,
- resourceClosingExecutor, future,
- mInferenceInfoStore));
- return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
- });
- result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
- resourceClosingExecutor);
- } finally {
- if (result == null) {
- resourceClosingExecutor.execute(() -> BundleUtil.tryCloseResource(request));
- }
- }
- }
-
- @Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
- this, in, out, err, args, callback, resultReceiver);
- }
- };
- }
-
- private void ensureRemoteIntelligenceServiceInitialized() {
- synchronized (mLock) {
- if (mRemoteOnDeviceIntelligenceService == null) {
- String serviceName = getServiceNames()[0];
- Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, false));
- mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
- ComponentName.unflattenFromString(serviceName),
- UserHandle.SYSTEM.getIdentifier());
- mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks(
- new ServiceConnector.ServiceLifecycleCallbacks<>() {
- @Override
- public void onConnected(
- @NonNull IOnDeviceIntelligenceService service) {
- try {
- service.registerRemoteServices(
- getRemoteProcessingService());
- service.ready();
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to send connected event", ex);
- }
- }
- });
- }
- }
- }
-
- @NonNull
- private IRemoteProcessingService.Stub getRemoteProcessingService() {
- return new IRemoteProcessingService.Stub() {
- @Override
- public void updateProcessingState(
- Bundle processingState,
- IProcessingUpdateStatusCallback callback) {
- callbackExecutor.execute(() -> {
- AndroidFuture<Void> result = null;
- try {
- sanitizeStateParams(processingState);
- ensureRemoteInferenceServiceInitialized();
- result = mRemoteInferenceService.post(
- service -> service.updateProcessingState(
- processingState, callback));
- result.whenCompleteAsync(
- (c, e) -> BundleUtil.tryCloseResource(processingState),
- resourceClosingExecutor);
- } finally {
- if (result == null) {
- resourceClosingExecutor.execute(
- () -> BundleUtil.tryCloseResource(processingState));
- }
- }
- });
- }
- };
- }
-
- private void ensureRemoteInferenceServiceInitialized() {
- synchronized (mLock) {
- if (mRemoteInferenceService == null) {
- String serviceName = getServiceNames()[1];
- Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, true));
- mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
- ComponentName.unflattenFromString(serviceName),
- UserHandle.SYSTEM.getIdentifier());
- mRemoteInferenceService.setServiceLifecycleCallbacks(
- new ServiceConnector.ServiceLifecycleCallbacks<>() {
- @Override
- public void onConnected(
- @NonNull IOnDeviceSandboxedInferenceService service) {
- try {
- ensureRemoteIntelligenceServiceInitialized();
- service.registerRemoteStorageService(
- getIRemoteStorageService(), new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle bundle) {
- final int uid = Binder.getCallingUid();
- setRemoteInferenceServiceUid(uid);
- }
- });
- mRemoteOnDeviceIntelligenceService.run(
- IOnDeviceIntelligenceService::notifyInferenceServiceConnected);
- broadcastExecutor.execute(
- () -> registerModelLoadingBroadcasts(service));
- mConfigExecutor.execute(
- () -> registerDeviceConfigChangeListener());
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to send connected event", ex);
- }
- }
-
- @Override
- public void onDisconnected(
- @NonNull IOnDeviceSandboxedInferenceService service) {
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.run(
- IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
- }
-
- @Override
- public void onBinderDied() {
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.run(
- IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
- }
- });
- }
- }
- }
-
- private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
- String[] modelBroadcastKeys;
- try {
- modelBroadcastKeys = getBroadcastKeys();
- } catch (Resources.NotFoundException e) {
- Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured.");
- return;
- }
-
- Bundle bundle = new Bundle();
- bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true);
- try {
- service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() {
- @Override
- public void onSuccess(PersistableBundle statusParams) {
- Binder.clearCallingIdentity();
- synchronized (mLock) {
- if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) {
- String modelLoadedBroadcastKey = modelBroadcastKeys[0];
- if (modelLoadedBroadcastKey != null
- && !modelLoadedBroadcastKey.isEmpty()) {
- final Intent intent = new Intent(modelLoadedBroadcastKey);
- intent.setPackage(mBroadcastPackageName);
- mContext.sendBroadcast(intent,
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
- }
- } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) {
- String modelUnloadedBroadcastKey = modelBroadcastKeys[1];
- if (modelUnloadedBroadcastKey != null
- && !modelUnloadedBroadcastKey.isEmpty()) {
- final Intent intent = new Intent(modelUnloadedBroadcastKey);
- intent.setPackage(mBroadcastPackageName);
- mContext.sendBroadcast(intent,
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
- }
- }
- }
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage) {
- Slog.e(TAG, "Failed to register model loading callback with status code",
- new OnDeviceIntelligenceException(errorCode, errorMessage));
- }
- });
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register model loading callback with status code", e);
- }
- }
-
- private void registerDeviceConfigChangeListener() {
- Log.d(TAG, "registerDeviceConfigChangeListener");
- String configNamespace = getConfigNamespace();
- if (configNamespace.isEmpty()) {
- Slog.e(TAG, "config_defaultOnDeviceIntelligenceDeviceConfigNamespace is empty");
- return;
- }
- DeviceConfig.addOnPropertiesChangedListener(
- configNamespace,
- mConfigExecutor,
- mOnPropertiesChangedListener);
- }
-
- private String getConfigNamespace() {
- synchronized (mLock) {
- if (mTemporaryConfigNamespace != null) {
- return mTemporaryConfigNamespace;
- }
-
- return mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace);
- }
- }
-
- private void sendUpdatedConfig(
- DeviceConfig.Properties props) {
- Log.d(TAG, "sendUpdatedConfig");
-
- PersistableBundle persistableBundle = new PersistableBundle();
- for (String key : props.getKeyset()) {
- persistableBundle.putString(key, props.getString(key, ""));
- }
- Bundle bundle = new Bundle();
- bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle);
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.run(service -> service.updateProcessingState(bundle,
- new IProcessingUpdateStatusCallback.Stub() {
- @Override
- public void onSuccess(PersistableBundle result) {
- Slog.d(TAG, "Config update successful." + result);
- }
-
- @Override
- public void onFailure(int errorCode, String errorMessage) {
- Slog.e(TAG, "Config update failed with code ["
- + String.valueOf(errorCode) + "] and message = " + errorMessage);
- }
- }));
- }
-
- @NonNull
- private IRemoteStorageService.Stub getIRemoteStorageService() {
- return new IRemoteStorageService.Stub() {
- @Override
- public void getReadOnlyFileDescriptor(
- String filePath,
- AndroidFuture<ParcelFileDescriptor> future) {
- ensureRemoteIntelligenceServiceInitialized();
- AndroidFuture<ParcelFileDescriptor> pfdFuture = new AndroidFuture<>();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.getReadOnlyFileDescriptor(
- filePath, pfdFuture));
- pfdFuture.whenCompleteAsync((pfd, error) -> {
- try {
- if (error != null) {
- future.completeExceptionally(error);
- } else {
- validatePfdReadOnly(pfd);
- future.complete(pfd);
- }
- } finally {
- tryClosePfd(pfd);
- }
- }, callbackExecutor);
- }
-
- @Override
- public void getReadOnlyFeatureFileDescriptorMap(
- Feature feature,
- RemoteCallback remoteCallback) {
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.getReadOnlyFeatureFileDescriptorMap(
- feature,
- new RemoteCallback(result -> callbackExecutor.execute(() -> {
- try {
- if (result == null) {
- remoteCallback.sendResult(null);
- }
- for (String key : result.keySet()) {
- ParcelFileDescriptor pfd = result.getParcelable(key,
- ParcelFileDescriptor.class);
- validatePfdReadOnly(pfd);
- }
- remoteCallback.sendResult(result);
- } finally {
- resourceClosingExecutor.execute(
- () -> BundleUtil.tryCloseResource(result));
- }
- }))));
- }
- };
- }
-
- private void validateServiceElevated(String serviceName, boolean checkIsolated) {
- try {
- if (TextUtils.isEmpty(serviceName)) {
- throw new IllegalStateException(
- "Remote service is not configured to complete the request");
- }
- ComponentName serviceComponent = ComponentName.unflattenFromString(
- serviceName);
- ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.SYSTEM.getIdentifier());
- if (serviceInfo != null) {
- if (!checkIsolated) {
- checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
- return;
- }
-
- checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
- if (!isIsolatedService(serviceInfo)) {
- throw new SecurityException(
- "Call required an isolated service, but the configured service: "
- + serviceName + ", is not isolated");
- }
- } else {
- throw new IllegalStateException(
- "Remote service is not configured to complete the request.");
- }
- } catch (RemoteException e) {
- throw new IllegalStateException("Could not fetch service info for remote services", e);
- }
- }
-
- private static void checkServiceRequiresPermission(ServiceInfo serviceInfo,
- String requiredPermission) {
- final String permission = serviceInfo.permission;
- if (!requiredPermission.equals(permission)) {
- throw new SecurityException(String.format(
- "Service %s requires %s permission. Found %s permission",
- serviceInfo.getComponentName(),
- requiredPermission,
- serviceInfo.permission));
- }
- }
-
- private static boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) {
- return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
- && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
- }
-
- private List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
- return mInferenceInfoStore.getLatestInferenceInfo(startTimeEpochMillis);
- }
-
- @Nullable
- public String getRemoteConfiguredPackageName() {
- try {
- String[] serviceNames = getServiceNames();
- ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]);
- if (componentName != null) {
- return componentName.getPackageName();
- }
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Could not find resource", e);
- }
-
- return null;
- }
-
-
- protected String[] getServiceNames() throws Resources.NotFoundException {
- // TODO 329240495 : Consider a small class with explicit field names for the two services
- synchronized (mLock) {
- if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) {
- return mTemporaryServiceNames;
- }
- }
- return new String[]{mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceService),
- mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService)};
- }
-
- protected String[] getBroadcastKeys() throws Resources.NotFoundException {
- // TODO 329240495 : Consider a small class with explicit field names for the two services
- synchronized (mLock) {
- if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) {
- return mTemporaryBroadcastKeys;
- }
- }
-
- return new String[]{mContext.getResources().getString(
- R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey),
- mContext.getResources().getString(
- R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)};
- }
-
- @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
- Objects.requireNonNull(componentNames);
- enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices");
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- synchronized (mLock) {
- mTemporaryServiceNames = componentNames;
- if (mRemoteInferenceService != null) {
- mRemoteInferenceService.unbind();
- mRemoteInferenceService = null;
- }
- if (mRemoteOnDeviceIntelligenceService != null) {
- mRemoteOnDeviceIntelligenceService.unbind();
- mRemoteOnDeviceIntelligenceService = null;
- }
-
- if (durationMs != -1) {
- getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE,
- durationMs);
- }
- }
- }
-
- @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName,
- int durationMs) {
- Objects.requireNonNull(broadcastKeys);
- enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys");
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- synchronized (mLock) {
- mTemporaryBroadcastKeys = broadcastKeys;
- mBroadcastPackageName = receiverPackageName;
- if (durationMs != -1) {
- getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs);
- }
- }
- }
-
- @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void setTemporaryDeviceConfigNamespace(@NonNull String configNamespace,
- int durationMs) {
- Objects.requireNonNull(configNamespace);
- enforceShellOnly(Binder.getCallingUid(), "setTemporaryDeviceConfigNamespace");
- mContext.enforceCallingPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- synchronized (mLock) {
- mTemporaryConfigNamespace = configNamespace;
- if (durationMs != -1) {
- getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_CONFIG_NAMESPACE,
- durationMs);
- }
- }
- }
-
- /**
- * Reset the temporary services set in CTS tests, this method is primarily used to only revert
- * the changes caused by CTS tests.
- */
- public void resetTemporaryServices() {
- synchronized (mLock) {
- if (mTemporaryHandler != null) {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- mTemporaryHandler = null;
- }
-
- mRemoteInferenceService = null;
- mRemoteOnDeviceIntelligenceService = null;
- mTemporaryServiceNames = new String[0];
- }
- }
-
- /**
- * Throws if the caller is not of a shell (or root) UID.
- *
- * @param callingUid pass Binder.callingUid().
- */
- public static void enforceShellOnly(int callingUid, String message) {
- if (callingUid == android.os.Process.SHELL_UID
- || callingUid == android.os.Process.ROOT_UID) {
- return; // okay
- }
-
- throw new SecurityException(message + ": Only shell user can call it");
- }
-
- private AndroidFuture<IBinder> wrapCancellationFuture(
- AndroidFuture future) {
- if (future == null) {
- return null;
- }
- AndroidFuture<IBinder> cancellationFuture = new AndroidFuture<>();
- cancellationFuture.whenCompleteAsync((c, e) -> {
- if (e != null) {
- Log.e(TAG, "Error forwarding ICancellationSignal to manager layer", e);
- future.completeExceptionally(e);
- } else {
- future.complete(new ICancellationSignal.Stub() {
- @Override
- public void cancel() throws RemoteException {
- ICancellationSignal.Stub.asInterface(c).cancel();
- }
- });
- }
- });
- return cancellationFuture;
- }
-
- private AndroidFuture<IBinder> wrapProcessingFuture(
- AndroidFuture future) {
- if (future == null) {
- return null;
- }
- AndroidFuture<IBinder> processingSignalFuture = new AndroidFuture<>();
- processingSignalFuture.whenCompleteAsync((c, e) -> {
- if (e != null) {
- future.completeExceptionally(e);
- } else {
- future.complete(new IProcessingSignal.Stub() {
- @Override
- public void sendSignal(PersistableBundle actionParams) throws RemoteException {
- IProcessingSignal.Stub.asInterface(c).sendSignal(actionParams);
- }
- });
- }
- });
- return processingSignalFuture;
- }
-
- private static void tryClosePfd(ParcelFileDescriptor pfd) {
- if (pfd != null) {
- try {
- pfd.close();
- } catch (IOException e) {
- Log.e(TAG, "Failed to close parcel file descriptor ", e);
- }
- }
- }
-
- private synchronized Handler getTemporaryHandler() {
- if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
- @Override
- public void handleMessage(Message msg) {
- synchronized (mLock) {
- if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
- resetTemporaryServices();
- } else if (msg.what == MSG_RESET_BROADCAST_KEYS) {
- mTemporaryBroadcastKeys = null;
- mBroadcastPackageName = SYSTEM_PACKAGE;
- } else if (msg.what == MSG_RESET_CONFIG_NAMESPACE) {
- mTemporaryConfigNamespace = null;
- } else {
- Slog.wtf(TAG, "invalid handler msg: " + msg);
- }
- }
- }
- };
- }
-
- return mTemporaryHandler;
- }
-
- private long getIdleTimeoutMs() {
- return Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
- mContext.getUserId());
- }
-
- private int getRemoteInferenceServiceUid() {
- synchronized (mLock) {
- return remoteInferenceServiceUid;
- }
- }
-
- private void setRemoteInferenceServiceUid(int remoteInferenceServiceUid) {
- synchronized (mLock) {
- this.remoteInferenceServiceUid = remoteInferenceServiceUid;
- }
- }
-}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
deleted file mode 100644
index d2c84fa1b18a..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence;
-
-import android.annotation.NonNull;
-import android.os.Binder;
-import android.os.ShellCommand;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-final class OnDeviceIntelligenceShellCommand extends ShellCommand {
- private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
-
- @NonNull
- private final OnDeviceIntelligenceManagerService mService;
-
- OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) {
- mService = service;
- }
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
- }
-
- switch (cmd) {
- case "set-temporary-services":
- return setTemporaryServices();
- case "get-services":
- return getConfiguredServices();
- case "set-model-broadcasts":
- return setBroadcastKeys();
- case "set-deviceconfig-namespace":
- return setDeviceConfigNamespace();
- default:
- return handleDefaultCommands(cmd);
- }
- }
-
- @Override
- public void onHelp() {
- PrintWriter pw = getOutPrintWriter();
- pw.println("OnDeviceIntelligenceShellCommand commands: ");
- pw.println(" help");
- pw.println(" Print this help text.");
- pw.println();
- pw.println(
- " set-temporary-services [IntelligenceServiceComponentName] "
- + "[InferenceServiceComponentName] [DURATION]");
- pw.println(" Temporarily (for DURATION ms) changes the service implementations.");
- pw.println(" To reset, call without any arguments.");
-
- pw.println(" get-services To get the names of services that are currently being used.");
- pw.println(
- " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] "
- + "[ReceiverPackageName] "
- + "[DURATION] To set the names of broadcast intent keys that are to be "
- + "emitted for cts tests.");
- pw.println(
- " set-deviceconfig-namespace [DeviceConfigNamespace] "
- + "[DURATION] To set the device config namespace "
- + "to use for cts tests.");
- }
-
- private int setTemporaryServices() {
- final PrintWriter out = getOutPrintWriter();
- final String intelligenceServiceName = getNextArg();
- final String inferenceServiceName = getNextArg();
-
- if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
- && inferenceServiceName == null) {
- OnDeviceIntelligenceManagerService.enforceShellOnly(Binder.getCallingUid(),
- "resetTemporaryServices");
- mService.resetTemporaryServices();
- out.println("OnDeviceIntelligenceManagerService temporary reset. ");
- return 0;
- }
-
- Objects.requireNonNull(intelligenceServiceName);
- Objects.requireNonNull(inferenceServiceName);
- final int duration = Integer.parseInt(getNextArgRequired());
- mService.setTemporaryServices(
- new String[]{intelligenceServiceName, inferenceServiceName},
- duration);
- out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
- + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
- + " for " + duration + "ms");
- return 0;
- }
-
- private int getConfiguredServices() {
- final PrintWriter out = getOutPrintWriter();
- String[] services = mService.getServiceNames();
- out.println("OnDeviceIntelligenceService set to : " + services[0]
- + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
- return 0;
- }
-
- private int setBroadcastKeys() {
- final PrintWriter out = getOutPrintWriter();
- final String modelLoadedKey = getNextArgRequired();
- final String modelUnloadedKey = getNextArgRequired();
- final String receiverPackageName = getNextArg();
-
- final int duration = Integer.parseInt(getNextArgRequired());
- mService.setModelBroadcastKeys(
- new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration);
- out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to "
- + modelLoadedKey
- + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey
- + "\n and Package name set to : " + receiverPackageName
- + " for " + duration + "ms");
- return 0;
- }
-
- private int setDeviceConfigNamespace() {
- final PrintWriter out = getOutPrintWriter();
- final String configNamespace = getNextArg();
-
- final int duration = Integer.parseInt(getNextArgRequired());
- mService.setTemporaryDeviceConfigNamespace(configNamespace, duration);
- out.println("OnDeviceIntelligence DeviceConfig Namespace temporarily set to "
- + configNamespace
- + " for " + duration + "ms");
- return 0;
- }
-
-} \ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
deleted file mode 100644
index ac9747aa83b3..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence;
-
-import static android.content.Context.BIND_FOREGROUND_SERVICE;
-import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
-
-import com.android.internal.infra.ServiceConnector;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Manages the connection to the remote on-device intelligence service. Also, handles unbinding
- * logic set by the service implementation via a Secure Settings flag.
- */
-public class RemoteOnDeviceIntelligenceService extends
- ServiceConnector.Impl<IOnDeviceIntelligenceService> {
- private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4);
- private static final String TAG =
- RemoteOnDeviceIntelligenceService.class.getSimpleName();
-
- RemoteOnDeviceIntelligenceService(Context context, ComponentName serviceName,
- int userId) {
- super(context, new Intent(
- OnDeviceIntelligenceService.SERVICE_INTERFACE).setComponent(serviceName),
- BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
- IOnDeviceIntelligenceService.Stub::asInterface);
-
- // Bind right away
- connect();
- }
-
- @Override
- protected long getRequestTimeoutMs() {
- return LONG_TIMEOUT;
- }
-
- @Override
- protected long getAutoDisconnectTimeoutMs() {
- return Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
- TimeUnit.SECONDS.toMillis(30),
- mContext.getUserId());
- }
-}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
deleted file mode 100644
index 18b13838ea7c..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence;
-
-import static android.content.Context.BIND_FOREGROUND_SERVICE;
-import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
-import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
-
-import com.android.internal.infra.ServiceConnector;
-
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * Manages the connection to the remote on-device sand boxed inference service. Also, handles
- * unbinding
- * logic set by the service implementation via a SecureSettings flag.
- */
-public class RemoteOnDeviceSandboxedInferenceService extends
- ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
- private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1);
-
- /**
- * Creates an instance of {@link ServiceConnector}
- *
- * See {@code protected} methods for optional parameters you can override.
- *
- * @param context to be used for {@link Context#bindServiceAsUser binding} and
- * {@link Context#unbindService unbinding}
- * @param userId to be used for {@link Context#bindServiceAsUser binding}
- */
- RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
- int userId) {
- super(context, new Intent(
- OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
- BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
- IOnDeviceSandboxedInferenceService.Stub::asInterface);
-
- // Bind right away
- connect();
- }
-
- @Override
- protected long getRequestTimeoutMs() {
- return LONG_TIMEOUT;
- }
-
-
- @Override
- protected long getAutoDisconnectTimeoutMs() {
- return Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
- TimeUnit.SECONDS.toMillis(30),
- mContext.getUserId());
- }
-}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
deleted file mode 100644
index 32f0698a8f9c..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.ondeviceintelligence.callbacks;
-
-import android.app.ondeviceintelligence.IDownloadCallback;
-import android.os.Handler;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback
- * such that, in the case where the callback methods are not invoked, we do not have to wait for
- * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in
- * some cases. Instead, in such cases we rely on the remote service sending progress updates and if
- * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
- * download will not complete and enabling faster cleanup.
- */
-public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
- private final IDownloadCallback callback;
- private final Handler handler;
- private final AndroidFuture future;
- private final long idleTimeoutMs;
-
- /**
- * Constructor to create a ListenableDownloadCallback.
- *
- * @param callback callback to send download updates to caller.
- * @param handler handler to schedule timeout runnable.
- * @param future future to complete to signal the callback has reached a terminal state.
- * @param idleTimeoutMs timeout within which download updates should be received.
- */
- public ListenableDownloadCallback(IDownloadCallback callback, Handler handler,
- AndroidFuture future,
- long idleTimeoutMs) {
- this.callback = callback;
- this.handler = handler;
- this.future = future;
- this.idleTimeoutMs = idleTimeoutMs;
- handler.postDelayed(this,
- idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked
- }
-
- @Override
- public void onDownloadStarted(long bytesToDownload) throws RemoteException {
- callback.onDownloadStarted(bytesToDownload);
- handler.removeCallbacks(this);
- handler.postDelayed(this, idleTimeoutMs);
- }
-
- @Override
- public void onDownloadProgress(long bytesDownloaded) throws RemoteException {
- callback.onDownloadProgress(bytesDownloaded);
- handler.removeCallbacks(this); // remove previously queued timeout tasks.
- handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update.
- }
-
- @Override
- public void onDownloadFailed(int failureStatus,
- String errorMessage, PersistableBundle errorParams) throws RemoteException {
- callback.onDownloadFailed(failureStatus, errorMessage, errorParams);
- handler.removeCallbacks(this);
- future.completeExceptionally(new TimeoutException());
- }
-
- @Override
- public void onDownloadCompleted(
- android.os.PersistableBundle downloadParams) throws RemoteException {
- callback.onDownloadCompleted(downloadParams);
- handler.removeCallbacks(this);
- future.complete(null);
- }
-
- @Override
- public void run() {
- future.completeExceptionally(
- new TimeoutException()); // complete the future as we haven't received updates
- // for download progress.
- }
-} \ No newline at end of file
diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
new file mode 100644
index 000000000000..871d12ee6394
--- /dev/null
+++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os.instrumentation;
+
+import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION;
+import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresPermission;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.IDynamicInstrumentationManager;
+import android.os.instrumentation.IOffsetCallback;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.MethodDescriptorParser;
+import android.os.instrumentation.TargetProcess;
+
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import dalvik.system.VMDebug;
+
+import java.lang.reflect.Method;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+
+/**
+ * System private implementation of the {@link IDynamicInstrumentationManager interface}.
+ */
+public class DynamicInstrumentationManagerService extends SystemService {
+
+ private ActivityManagerInternal mAmInternal;
+
+ public DynamicInstrumentationManagerService(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+ publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService());
+ }
+
+ private final class BinderService extends IDynamicInstrumentationManager.Stub {
+ @Override
+ @PermissionManuallyEnforced
+ @RequiresPermission(value = android.Manifest.permission.DYNAMIC_INSTRUMENTATION)
+ public void getExecutableMethodFileOffsets(
+ @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor,
+ @NonNull IOffsetCallback callback) {
+ if (!com.android.art.flags.Flags.executableMethodFileOffsets()) {
+ throw new UnsupportedOperationException();
+ }
+ getContext().enforceCallingOrSelfPermission(
+ DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission");
+ Objects.requireNonNull(targetProcess.processName);
+
+ if (!targetProcess.processName.equals("system_server")) {
+ try {
+ mAmInternal.getExecutableMethodFileOffsets(targetProcess.processName,
+ targetProcess.pid, targetProcess.uid, methodDescriptor,
+ new IOffsetCallback.Stub() {
+ @Override
+ public void onResult(ExecutableMethodFileOffsets result) {
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+ });
+ return;
+ } catch (NoSuchElementException e) {
+ throw new IllegalArgumentException(
+ "The specified app process cannot be found." , e);
+ }
+ }
+
+ Method method = MethodDescriptorParser.parseMethodDescriptor(
+ getClass().getClassLoader(), methodDescriptor);
+ VMDebug.ExecutableMethodFileOffsets location =
+ VMDebug.getExecutableMethodFileOffsets(method);
+
+ try {
+ if (location == null) {
+ callback.onResult(null);
+ return;
+ }
+
+ ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
+ ret.containerPath = location.getContainerPath();
+ ret.containerOffset = location.getContainerOffset();
+ ret.methodOffset = location.getMethodOffset();
+ callback.onResult(ret);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to invoke result callback", e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java b/services/core/java/com/android/server/pinner/PinRangeSource.java
index 1450dc0803d6..5f9641122294 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
+++ b/services/core/java/com/android/server/pinner/PinRangeSource.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.server.ondeviceintelligence;
+package com.android.server.pinner;
-public interface OnDeviceIntelligenceManagerInternal {
+/* package */ abstract class PinRangeSource {
/**
- * Gets the uid for the process that is currently hosting the
- * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on
- * the device.
+ * Retrieve a range to pin.
+ *
+ * @param outPinRange Receives the pin region
+ * @return True if we filled in outPinRange or false if we're out of pin entries
*/
- int getInferenceServiceUid();
+ abstract boolean read(PinnerService.PinRange outPinRange);
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java b/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java
new file mode 100644
index 000000000000..d6fc48790883
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+/* package */ class PinRangeSourceStatic extends PinRangeSource {
+ private final int mPinStart;
+ private final int mPinLength;
+ private boolean mDone = false;
+
+ PinRangeSourceStatic(int pinStart, int pinLength) {
+ mPinStart = pinStart;
+ mPinLength = pinLength;
+ }
+
+ @Override
+ boolean read(PinnerService.PinRange outPinRange) {
+ outPinRange.start = mPinStart;
+ outPinRange.length = mPinLength;
+ boolean done = mDone;
+ mDone = true;
+ return !done;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinRangeSourceStream.java b/services/core/java/com/android/server/pinner/PinRangeSourceStream.java
new file mode 100644
index 000000000000..79900b9de463
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSourceStream.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/* package */ final class PinRangeSourceStream extends PinRangeSource {
+ private final DataInputStream mStream;
+ private boolean mDone = false;
+
+ PinRangeSourceStream(InputStream stream) {
+ mStream = new DataInputStream(stream);
+ }
+
+ @Override
+ boolean read(PinnerService.PinRange outPinRange) {
+ if (!mDone) {
+ try {
+ outPinRange.start = mStream.readInt();
+ outPinRange.length = mStream.readInt();
+ } catch (IOException ex) {
+ mDone = true;
+ }
+ }
+ return !mDone;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinnedFile.java b/services/core/java/com/android/server/pinner/PinnedFile.java
new file mode 100644
index 000000000000..a8de344d10af
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinnedFile.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+@VisibleForTesting
+public final class PinnedFile implements AutoCloseable {
+ private long mAddress;
+ final long mapSize;
+ final String fileName;
+ public final long bytesPinned;
+
+ // Whether this file was pinned using a pinlist
+ boolean used_pinlist;
+
+ // User defined group name for pinner accounting
+ String groupName = "";
+ ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
+
+ public PinnedFile(long address, long mapSize, String fileName, long bytesPinned) {
+ mAddress = address;
+ this.mapSize = mapSize;
+ this.fileName = fileName;
+ this.bytesPinned = bytesPinned;
+ }
+
+ @Override
+ public void close() {
+ if (mAddress >= 0) {
+ PinnerUtils.safeMunmap(mAddress, mapSize);
+ mAddress = -1;
+ }
+ for (PinnedFile dep : pinnedDeps) {
+ if (dep != null) {
+ dep.close();
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ close();
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/pinner/PinnerService.java
index ef03888d6620..2c75926c4943 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/pinner/PinnerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pinner;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.os.Process.SYSTEM_UID;
+import static com.android.server.flags.Flags.pinGlobalQuota;
import static com.android.server.flags.Flags.pinWebview;
-import static com.android.server.flags.Flags.skipHomeArtPins;
import android.annotation.EnforcePermission;
import android.annotation.IntDef;
@@ -49,6 +49,7 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -72,13 +73,13 @@ import com.android.internal.app.ResolverActivity;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
-import java.io.Closeable;
-import java.io.DataInputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -110,8 +111,7 @@ public final class PinnerService extends SystemService {
private static final String PIN_META_FILENAME = "pinlist.meta";
private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
private static final int KEY_CAMERA = 0;
private static final int KEY_HOME = 1;
@@ -121,11 +121,10 @@ public final class PinnerService extends SystemService {
private static boolean PROP_PIN_PINLIST =
SystemProperties.getBoolean("pinner.use_pinlist", true);
- private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
- private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app.
-
public static final String ANON_REGION_STAT_NAME = "[anon]";
+ private static final String SYSTEM_GROUP_NAME = "system";
+
@IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT})
@Retention(RetentionPolicy.SOURCE)
public @interface AppKey {}
@@ -139,7 +138,8 @@ public final class PinnerService extends SystemService {
private final UserManager mUserManager;
/** The list of the statically pinned files. */
- @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
+ @GuardedBy("this")
+ private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
/** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
@GuardedBy("this")
@@ -159,8 +159,8 @@ public final class PinnerService extends SystemService {
/**
* A set of {@link AppKey} that are configured to be pinned.
*/
- @GuardedBy("this")
- private ArraySet<Integer> mPinKeys;
+ @GuardedBy("this") private
+ ArraySet<Integer> mPinKeys;
// Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want
// them to be responsive to dynamic flag changes for experimentation.
@@ -176,18 +176,29 @@ public final class PinnerService extends SystemService {
// Resource-configured pinner flags;
private final boolean mConfiguredToPinCamera;
+ private final int mConfiguredCameraPinBytes;
private final int mConfiguredHomePinBytes;
private final boolean mConfiguredToPinAssistant;
+ private final int mConfiguredAssistantPinBytes;
private final int mConfiguredWebviewPinBytes;
+ // This is the percentage of total device memory that will be used to set the global quota.
+ private final int mConfiguredMaxPinnedMemoryPercentage;
+
+ // This is the global pinner quota that can be pinned.
+ private long mConfiguredMaxPinnedMemory;
+
+ // This is the currently pinned memory.
+ private long mCurrentPinnedMemory = 0;
+
private BinderService mBinderService;
private PinnerHandler mPinnerHandler = null;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- // If an app has updated, update pinned files accordingly.
- if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
+ // If an app has updated, update pinned files accordingly.
+ if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Uri packageUri = intent.getData();
String packageName = packageUri.getSchemeSpecificPart();
ArraySet<String> updatedPackages = new ArraySet<>();
@@ -210,7 +221,7 @@ public final class PinnerService extends SystemService {
/** Utility class for testing. */
@VisibleForTesting
- static class Injector {
+ public static class Injector {
protected DeviceConfigInterface getDeviceConfigInterface() {
return DeviceConfigInterface.REAL;
}
@@ -219,9 +230,9 @@ public final class PinnerService extends SystemService {
service.publishBinderService("pinner", binderService);
}
- protected PinnedFile pinFileInternal(String fileToPin,
- int maxBytesToPin, boolean attemptPinIntrospection) {
- return PinnerService.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
+ protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
+ long maxBytesToPin, boolean attemptPinIntrospection) {
+ return service.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
}
}
@@ -230,7 +241,7 @@ public final class PinnerService extends SystemService {
}
@VisibleForTesting
- PinnerService(Context context, Injector injector) {
+ public PinnerService(Context context, Injector injector) {
super(context);
mContext = context;
@@ -238,12 +249,19 @@ public final class PinnerService extends SystemService {
mDeviceConfigInterface = mInjector.getDeviceConfigInterface();
mConfiguredToPinCamera = context.getResources().getBoolean(
com.android.internal.R.bool.config_pinnerCameraApp);
+ mConfiguredCameraPinBytes = context.getResources().getInteger(
+ com.android.internal.R.integer.config_pinnerCameraPinBytes);
+ mConfiguredAssistantPinBytes = context.getResources().getInteger(
+ com.android.internal.R.integer.config_pinnerAssistantPinBytes);
mConfiguredHomePinBytes = context.getResources().getInteger(
com.android.internal.R.integer.config_pinnerHomePinBytes);
mConfiguredToPinAssistant = context.getResources().getBoolean(
com.android.internal.R.bool.config_pinnerAssistantApp);
mConfiguredWebviewPinBytes = context.getResources().getInteger(
com.android.internal.R.integer.config_pinnerWebviewPinBytes);
+ mConfiguredMaxPinnedMemoryPercentage = context.getResources().getInteger(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage);
+
mPinKeys = createPinKeys();
mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
@@ -261,10 +279,8 @@ public final class PinnerService extends SystemService {
registerUidListener();
registerUserSetupCompleteListener();
- mDeviceConfigInterface.addOnPropertiesChangedListener(
- DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
- new HandlerExecutor(mPinnerHandler),
- mDeviceConfigAnonSizeListener);
+ mDeviceConfigInterface.addOnPropertiesChangedListener(DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
+ new HandlerExecutor(mPinnerHandler), mDeviceConfigAnonSizeListener);
}
@Override
@@ -272,6 +288,10 @@ public final class PinnerService extends SystemService {
if (DEBUG) {
Slog.i(TAG, "Starting PinnerService");
}
+ mConfiguredMaxPinnedMemory =
+ (Process.getTotalMemory()
+ * Math.clamp(mConfiguredMaxPinnedMemoryPercentage, 0, 100))
+ / 100;
mBinderService = new BinderService();
mInjector.publishBinderService(this, mBinderService);
publishLocalService(PinnerService.class, this);
@@ -348,7 +368,7 @@ public final class PinnerService extends SystemService {
protected PinnedFileStats(int uid, PinnedFile file) {
this.uid = uid;
this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1);
- this.sizeKb = file.bytesPinned / 1024;
+ this.sizeKb = (int) file.bytesPinned / 1024;
}
}
@@ -358,20 +378,11 @@ public final class PinnerService extends SystemService {
private void handlePinOnStart() {
// Files to pin come from the overlay and can be specified per-device config
String[] filesToPin = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_defaultPinnerServiceFiles);
+ com.android.internal.R.array.config_defaultPinnerServiceFiles);
// Continue trying to pin each file even if we fail to pin some of them
for (String fileToPin : filesToPin) {
- PinnedFile pf = mInjector.pinFileInternal(fileToPin, Integer.MAX_VALUE,
- /*attemptPinIntrospection=*/false);
- if (pf == null) {
- Slog.e(TAG, "Failed to pin file = " + fileToPin);
- continue;
- }
- synchronized (this) {
- mPinnedFiles.put(pf.fileName, pf);
- }
- pf.groupName = "system";
- pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, null);
+ pinFile(fileToPin, Integer.MAX_VALUE, /*appInfo=*/null, /*groupName=*/SYSTEM_GROUP_NAME,
+ true);
}
refreshPinAnonConfig();
@@ -383,10 +394,9 @@ public final class PinnerService extends SystemService {
* regular home app.
*/
private void registerUserSetupCompleteListener() {
- Uri userSetupCompleteUri = Settings.Secure.getUriFor(
- Settings.Secure.USER_SETUP_COMPLETE);
- mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
- false, new ContentObserver(null) {
+ Uri userSetupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
+ mContext.getContentResolver().registerContentObserver(
+ userSetupCompleteUri, false, new ContentObserver(null) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (userSetupCompleteUri.equals(uri)) {
@@ -409,7 +419,7 @@ public final class PinnerService extends SystemService {
}
@Override
- public void onUidActive(int uid) {
+ public void onUidActive(int uid) {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
PinnerService::handleUidActive, PinnerService.this, uid));
}
@@ -423,7 +433,6 @@ public final class PinnerService extends SystemService {
updateActiveState(uid, false /* active */);
int key;
synchronized (this) {
-
// In case we have a pending repin, repin now. See mPendingRepin for more information.
key = mPendingRepin.getOrDefault(uid, -1);
if (key == -1) {
@@ -491,8 +500,8 @@ public final class PinnerService extends SystemService {
private ApplicationInfo getCameraInfo(int userHandle) {
Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
- ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
- false /* defaultToSystemApp */);
+ ApplicationInfo info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, false /* defaultToSystemApp */);
// If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
// We don't use _SECURE first because it will never get set on a device
@@ -501,16 +510,16 @@ public final class PinnerService extends SystemService {
// preference using this intent.
if (info == null) {
cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
- info = getApplicationInfoForIntent(cameraIntent, userHandle,
- false /* defaultToSystemApp */);
+ info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, false /* defaultToSystemApp */);
}
// If the _SECURE intent doesn't resolve, try the original intent but request
// the system app for camera if there was more than one result.
if (info == null) {
cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
- info = getApplicationInfoForIntent(cameraIntent, userHandle,
- true /* defaultToSystemApp */);
+ info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, true /* defaultToSystemApp */);
}
return info;
}
@@ -525,14 +534,14 @@ public final class PinnerService extends SystemService {
return getApplicationInfoForIntent(intent, userHandle, true);
}
- private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
- boolean defaultToSystemApp) {
+ private ApplicationInfo getApplicationInfoForIntent(
+ Intent intent, int userHandle, boolean defaultToSystemApp) {
if (intent == null) {
return null;
}
- ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
- MATCH_FLAGS, userHandle);
+ ResolveInfo resolveInfo =
+ mContext.getPackageManager().resolveActivityAsUser(intent, MATCH_FLAGS, userHandle);
// If this intent can resolve to only one app, choose that one.
// Otherwise, if we've requested to default to the system app, return it;
@@ -547,12 +556,11 @@ public final class PinnerService extends SystemService {
}
if (defaultToSystemApp) {
- List<ResolveInfo> infoList = mContext.getPackageManager()
- .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
+ List<ResolveInfo> infoList = mContext.getPackageManager().queryIntentActivitiesAsUser(
+ intent, MATCH_FLAGS, userHandle);
ApplicationInfo systemAppInfo = null;
for (ResolveInfo info : infoList) {
- if ((info.activityInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if ((info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
if (systemAppInfo == null) {
systemAppInfo = info.activityInfo.applicationInfo;
} else {
@@ -568,13 +576,13 @@ public final class PinnerService extends SystemService {
}
private void sendPinAppsMessage(int userHandle) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
- userHandle));
+ mPinnerHandler.sendMessage(
+ PooledLambda.obtainMessage(PinnerService::pinApps, this, userHandle));
}
private void sendPinAppsWithUpdatedKeysMessage(int userHandle) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinAppsWithUpdatedKeys,
- this, userHandle));
+ mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
+ PinnerService::pinAppsWithUpdatedKeys, this, userHandle));
}
private void sendUnpinAppsMessage() {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::unpinApps, this));
@@ -586,8 +594,7 @@ public final class PinnerService extends SystemService {
// phenotype property is not set.
boolean shouldPinCamera = mConfiguredToPinCamera
&& mDeviceConfigInterface.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
- "pin_camera",
- SystemProperties.getBoolean("pinner.pin_camera", true));
+ "pin_camera", SystemProperties.getBoolean("pinner.pin_camera", true));
if (shouldPinCamera) {
pinKeys.add(KEY_CAMERA);
} else if (DEBUG) {
@@ -626,8 +633,9 @@ public final class PinnerService extends SystemService {
synchronized (this) {
// This code path demands preceding unpinApps() call.
if (!mPinnedApps.isEmpty()) {
- Slog.e(TAG, "Attempted to update a list of apps, "
- + "but apps were already pinned. Skipping.");
+ Slog.e(TAG,
+ "Attempted to update a list of apps, "
+ + "but apps were already pinned. Skipping.");
return;
}
@@ -646,8 +654,8 @@ public final class PinnerService extends SystemService {
* @see #pinApp(int, int, boolean)
*/
private void sendPinAppMessage(int key, int userHandle, boolean force) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
- key, userHandle, force));
+ mPinnerHandler.sendMessage(
+ PooledLambda.obtainMessage(PinnerService::pinApp, this, key, userHandle, force));
}
/**
@@ -667,10 +675,10 @@ public final class PinnerService extends SystemService {
}
return;
}
- unpinApp(key);
ApplicationInfo info = getInfoForKey(key, userHandle);
+ unpinApp(key);
if (info != null) {
- pinApp(key, info);
+ pinAppInternal(key, info);
}
}
@@ -682,9 +690,7 @@ public final class PinnerService extends SystemService {
private int getUidForKey(@AppKey int key) {
synchronized (this) {
PinnedApp existing = mPinnedApps.get(key);
- return existing != null && existing.active
- ? existing.uid
- : -1;
+ return existing != null && existing.active ? existing.uid : -1;
}
}
@@ -727,11 +733,8 @@ public final class PinnerService extends SystemService {
* Handle any changes in the anon region pinner config.
*/
private void refreshPinAnonConfig() {
- long newPinAnonSize =
- mDeviceConfigInterface.getLong(
- DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
- DEVICE_CONFIG_KEY_ANON_SIZE,
- DEFAULT_ANON_SIZE);
+ long newPinAnonSize = mDeviceConfigInterface.getLong(
+ DEVICE_CONFIG_NAMESPACE_ANON_SIZE, DEVICE_CONFIG_KEY_ANON_SIZE, DEFAULT_ANON_SIZE);
newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
if (newPinAnonSize != mPinAnonSize) {
mPinAnonSize = newPinAnonSize;
@@ -765,10 +768,9 @@ public final class PinnerService extends SystemService {
try {
// Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status).
// The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo.
- address = Os.mmap(0, alignedPinSize,
- OsConstants.PROT_READ | OsConstants.PROT_WRITE,
- OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS,
- new FileDescriptor(), /*offset=*/0);
+ address = Os.mmap(0, alignedPinSize, OsConstants.PROT_READ | OsConstants.PROT_WRITE,
+ OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS, new FileDescriptor(),
+ /*offset=*/0);
Unsafe tempUnsafe = null;
Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
@@ -794,14 +796,14 @@ public final class PinnerService extends SystemService {
return;
} finally {
if (address >= 0) {
- safeMunmap(address, alignedPinSize);
+ PinnerUtils.safeMunmap(address, alignedPinSize);
}
}
}
private void unpinAnonRegion() {
if (mPinAnonAddress != 0) {
- safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
+ PinnerUtils.safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
}
mPinAnonAddress = 0;
mCurrentlyPinnedAnonSize = 0;
@@ -813,23 +815,31 @@ public final class PinnerService extends SystemService {
private int getSizeLimitForKey(@AppKey int key) {
switch (key) {
case KEY_CAMERA:
- return MAX_CAMERA_PIN_SIZE;
+ return mConfiguredCameraPinBytes;
case KEY_HOME:
return mConfiguredHomePinBytes;
case KEY_ASSISTANT:
- return MAX_ASSISTANT_PIN_SIZE;
+ return mConfiguredAssistantPinBytes;
default:
return 0;
}
}
/**
+ * Retrieves remaining quota for pinner service, once it reaches 0 it will no longer
+ * pin any file.
+ */
+ private long getAvailableGlobalQuota() {
+ return mConfiguredMaxPinnedMemory - mCurrentPinnedMemory;
+ }
+
+ /**
* Pins an application.
*
* @param key The key of the app to pin.
* @param appInfo The corresponding app info.
*/
- private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
+ private void pinAppInternal(@AppKey int key, @Nullable ApplicationInfo appInfo) {
if (appInfo == null) {
return;
}
@@ -839,7 +849,6 @@ public final class PinnerService extends SystemService {
mPinnedApps.put(key, pinnedApp);
}
-
// pin APK
final int pinSizeLimit = getSizeLimitForKey(key);
List<String> apks = new ArrayList<>();
@@ -851,36 +860,31 @@ public final class PinnerService extends SystemService {
}
}
- int apkPinSizeLimit = pinSizeLimit;
+ long apkPinSizeLimit = pinSizeLimit;
- boolean shouldSkipArtPins = key == KEY_HOME && skipHomeArtPins();
-
- for (String apk: apks) {
+ for (String apk : apks) {
if (apkPinSizeLimit <= 0) {
Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk);
// Continue instead of break to print all skipped APK names.
continue;
}
- PinnedFile pf = mInjector.pinFileInternal(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true);
+ String pinGroup = getNameForKey(key);
+ boolean shouldPinDeps = apk.equals(appInfo.sourceDir);
+ PinnedFile pf = pinFile(apk, apkPinSizeLimit, appInfo, pinGroup, shouldPinDeps);
if (pf == null) {
Slog.e(TAG, "Failed to pin " + apk);
continue;
}
- pf.groupName = getNameForKey(key);
if (DEBUG) {
Slog.i(TAG, "Pinned " + pf.fileName);
}
synchronized (this) {
pinnedApp.mFiles.add(pf);
- mPinnedFiles.put(pf.fileName, pf);
}
apkPinSizeLimit -= pf.bytesPinned;
- if (apk.equals(appInfo.sourceDir) && !shouldSkipArtPins) {
- pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, appInfo);
- }
}
}
@@ -892,19 +896,23 @@ public final class PinnerService extends SystemService {
* that related to the file but not within itself.
*
* @param fileToPin File to pin
- * @param maxBytesToPin maximum quota allowed for pinning
- * @return total bytes that were pinned.
+ * @param bytesRequestedToPin maximum bytes requested to pin for {@code fileToPin}.
+ * @param pinOptimizedDeps whether optimized dependencies such as odex,vdex, etc be pinned.
+ * Note: {@code bytesRequestedToPin} limit will not apply to optimized
+ * dependencies pinned, only global quotas will apply instead.
+ * @return pinned file
*/
- public int pinFile(String fileToPin, int maxBytesToPin, @Nullable ApplicationInfo appInfo,
- @Nullable String groupName) {
+ public PinnedFile pinFile(String fileToPin, long bytesRequestedToPin,
+ @Nullable ApplicationInfo appInfo, @Nullable String groupName,
+ boolean pinOptimizedDeps) {
PinnedFile existingPin;
- synchronized(this) {
+ synchronized (this) {
existingPin = mPinnedFiles.get(fileToPin);
}
if (existingPin != null) {
- if (existingPin.bytesPinned == maxBytesToPin) {
+ if (existingPin.bytesPinned == bytesRequestedToPin) {
// Duplicate pin requesting same amount of bytes, lets just bail out.
- return 0;
+ return null;
} else {
// User decided to pin a different amount of bytes than currently pinned
// so this is a valid pin request. Unpin the previous version before repining.
@@ -915,26 +923,38 @@ public final class PinnerService extends SystemService {
}
}
+ long remainingQuota = getAvailableGlobalQuota();
+
+ if (pinGlobalQuota()) {
+ if (remainingQuota <= 0) {
+ Slog.w(TAG, "Reached pin quota, skipping file: " + fileToPin);
+ return null;
+ }
+ bytesRequestedToPin = Math.min(bytesRequestedToPin, remainingQuota);
+ }
+
boolean isApk = fileToPin.endsWith(".apk");
- int bytesPinned = 0;
- PinnedFile pf = mInjector.pinFileInternal(fileToPin, maxBytesToPin,
+
+ PinnedFile pf = mInjector.pinFileInternal(this, fileToPin, bytesRequestedToPin,
/*attemptPinIntrospection=*/isApk);
if (pf == null) {
Slog.e(TAG, "Failed to pin file = " + fileToPin);
- return 0;
+ return null;
}
pf.groupName = groupName != null ? groupName : "";
- bytesPinned += pf.bytesPinned;
- maxBytesToPin -= bytesPinned;
+ mCurrentPinnedMemory += pf.bytesPinned;
synchronized (this) {
mPinnedFiles.put(pf.fileName, pf);
}
- if (maxBytesToPin > 0) {
- pinOptimizedDexDependencies(pf, maxBytesToPin, appInfo);
+
+ if (pinOptimizedDeps) {
+ mCurrentPinnedMemory +=
+ pinOptimizedDexDependencies(pf, getAvailableGlobalQuota(), appInfo);
}
- return bytesPinned;
+
+ return pf;
}
/**
@@ -945,13 +965,13 @@ public final class PinnerService extends SystemService {
* to null it will use the default supported ABI by the device.
* @return total bytes pinned.
*/
- private int pinOptimizedDexDependencies(
- PinnedFile pinnedFile, int maxBytesToPin, @Nullable ApplicationInfo appInfo) {
+ private long pinOptimizedDexDependencies(
+ PinnedFile pinnedFile, long maxBytesToPin, @Nullable ApplicationInfo appInfo) {
if (pinnedFile == null) {
return 0;
}
- int bytesPinned = 0;
+ long bytesPinned = 0;
if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) {
String abi = null;
if (appInfo != null) {
@@ -974,7 +994,7 @@ public final class PinnerService extends SystemService {
// Unpin if it was already pinned prior to re-pinning.
unpinFile(file);
- PinnedFile df = mInjector.pinFileInternal(file, maxBytesToPin,
+ PinnedFile df = mInjector.pinFileInternal(this, file, maxBytesToPin,
/*attemptPinIntrospection=*/false);
if (df == null) {
Slog.i(TAG, "Failed to pin ART file = " + file);
@@ -992,7 +1012,8 @@ public final class PinnerService extends SystemService {
return bytesPinned;
}
- /** mlock length bytes of fileToPin in memory
+ /**
+ * mlock length bytes of fileToPin in memory
*
* If attemptPinIntrospection is true, then treat the file to pin as a zip file and
* look for a "pinlist.meta" file in the archive root directory. The structure of this
@@ -1029,8 +1050,8 @@ public final class PinnerService extends SystemService {
* zip in order to extract the
* @return Pinned memory resource owner thing or null on error
*/
- private static PinnedFile pinFileInternal(
- String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) {
+ private PinnedFile pinFileInternal(
+ String fileToPin, long maxBytesToPin, boolean attemptPinIntrospection) {
if (DEBUG) {
Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection);
}
@@ -1054,8 +1075,8 @@ public final class PinnerService extends SystemService {
}
return pinnedFile;
} finally {
- safeClose(pinRangeStream);
- safeClose(fileAsZip); // Also closes any streams we've opened
+ PinnerUtils.safeClose(pinRangeStream);
+ PinnerUtils.safeClose(fileAsZip); // Also closes any streams we've opened
}
}
@@ -1068,11 +1089,8 @@ public final class PinnerService extends SystemService {
try {
zip = new ZipFile(fileName);
} catch (IOException ex) {
- Slog.w(TAG,
- String.format(
- "could not open \"%s\" as zip: pinning as blob",
- fileName),
- ex);
+ Slog.w(TAG, String.format("could not open \"%s\" as zip: pinning as blob", fileName),
+ ex);
}
return zip;
}
@@ -1112,9 +1130,9 @@ public final class PinnerService extends SystemService {
pinMetaStream = zipFile.getInputStream(pinMetaEntry);
} catch (IOException ex) {
Slog.w(TAG,
- String.format("error reading pin metadata \"%s\": pinning as blob",
- fileName),
- ex);
+ String.format(
+ "error reading pin metadata \"%s\": pinning as blob", fileName),
+ ex);
}
} else {
Slog.w(TAG,
@@ -1124,57 +1142,6 @@ public final class PinnerService extends SystemService {
return pinMetaStream;
}
- private static abstract class PinRangeSource {
- /** Retrive a range to pin.
- *
- * @param outPinRange Receives the pin region
- * @return True if we filled in outPinRange or false if we're out of pin entries
- */
- abstract boolean read(PinRange outPinRange);
- }
-
- private static final class PinRangeSourceStatic extends PinRangeSource {
- private final int mPinStart;
- private final int mPinLength;
- private boolean mDone = false;
-
- PinRangeSourceStatic(int pinStart, int pinLength) {
- mPinStart = pinStart;
- mPinLength = pinLength;
- }
-
- @Override
- boolean read(PinRange outPinRange) {
- outPinRange.start = mPinStart;
- outPinRange.length = mPinLength;
- boolean done = mDone;
- mDone = true;
- return !done;
- }
- }
-
- private static final class PinRangeSourceStream extends PinRangeSource {
- private final DataInputStream mStream;
- private boolean mDone = false;
-
- PinRangeSourceStream(InputStream stream) {
- mStream = new DataInputStream(stream);
- }
-
- @Override
- boolean read(PinRange outPinRange) {
- if (!mDone) {
- try {
- outPinRange.start = mStream.readInt();
- outPinRange.length = mStream.readInt();
- } catch (IOException ex) {
- mDone = true;
- }
- }
- return !mDone;
- }
- }
-
/**
* Helper for pinFile.
*
@@ -1185,25 +1152,20 @@ public final class PinnerService extends SystemService {
* @return PinnedFile or null on error
*/
private static PinnedFile pinFileRanges(
- String fileToPin,
- int maxBytesToPin,
- PinRangeSource pinRangeSource)
- {
+ String fileToPin, long maxBytesToPin, PinRangeSource pinRangeSource) {
FileDescriptor fd = new FileDescriptor();
long address = -1;
- int mapSize = 0;
+ long mapSize = 0;
try {
int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC);
fd = Os.open(fileToPin, openFlags, 0);
mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
- address = Os.mmap(0, mapSize,
- OsConstants.PROT_READ,
- OsConstants.MAP_SHARED,
- fd, /*offset=*/0);
+ address = Os.mmap(
+ 0, mapSize, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, /*offset=*/0);
PinRange pinRange = new PinRange();
- int bytesPinned = 0;
+ long bytesPinned = 0;
// We pin at page granularity, so make sure the limit is page-aligned
if (maxBytesToPin % PAGE_SIZE != 0) {
@@ -1211,10 +1173,10 @@ public final class PinnerService extends SystemService {
}
while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
- int pinStart = pinRange.start;
- int pinLength = pinRange.length;
- pinStart = clamp(0, pinStart, mapSize);
- pinLength = clamp(0, pinLength, mapSize - pinStart);
+ long pinStart = pinRange.start;
+ long pinLength = pinRange.length;
+ pinStart = PinnerUtils.clamp(0, pinStart, mapSize);
+ pinLength = PinnerUtils.clamp(0, pinLength, mapSize - pinStart);
pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
// mlock doesn't require the region to be page-aligned, but we snap the
@@ -1229,14 +1191,13 @@ public final class PinnerService extends SystemService {
if (pinLength % PAGE_SIZE != 0) {
pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
}
- pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
+ pinLength = PinnerUtils.clamp(0, pinLength, maxBytesToPin - bytesPinned);
if (pinLength > 0) {
if (DEBUG) {
Slog.d(TAG,
- String.format(
- "pinning at %s %s bytes of %s",
- pinStart, pinLength, fileToPin));
+ String.format("pinning at %s %s bytes of %s", pinStart, pinLength,
+ fileToPin));
}
Os.mlock(address + pinStart, pinLength);
}
@@ -1244,15 +1205,15 @@ public final class PinnerService extends SystemService {
}
PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
- address = -1; // Ownership transferred
+ address = -1; // Ownership transferred
return pinnedFile;
} catch (ErrnoException ex) {
Slog.e(TAG, "Could not pin file " + fileToPin, ex);
return null;
} finally {
- safeClose(fd);
+ PinnerUtils.safeClose(fd);
if (address >= 0) {
- safeMunmap(address, mapSize);
+ PinnerUtils.safeMunmap(address, mapSize);
}
}
}
@@ -1273,81 +1234,50 @@ public final class PinnerService extends SystemService {
}
}
- public void unpinFile(String filename) {
+ /**
+ * Unpin a file and its optimized dependencies.
+ *
+ * @param filename file to unpin.
+ * @return number of bytes unpinned, 0 in case of failure or nothing to unpin.
+ */
+ public long unpinFile(String filename) {
PinnedFile pinnedFile;
synchronized (this) {
pinnedFile = mPinnedFiles.get(filename);
}
if (pinnedFile == null) {
// File not pinned, nothing to do.
- return;
+ return 0;
}
+ long unpinnedBytes = pinnedFile.bytesPinned;
pinnedFile.close();
synchronized (this) {
if (DEBUG) {
Slog.d(TAG, "Unpinned file: " + filename);
}
+ mCurrentPinnedMemory -= pinnedFile.bytesPinned;
+
mPinnedFiles.remove(pinnedFile.fileName);
for (PinnedFile dep : pinnedFile.pinnedDeps) {
if (dep == null) {
continue;
}
+ unpinnedBytes -= dep.bytesPinned;
+ mCurrentPinnedMemory -= dep.bytesPinned;
mPinnedFiles.remove(dep.fileName);
if (DEBUG) {
Slog.d(TAG, "Unpinned dependency: " + dep.fileName);
}
}
}
- }
- private static int clamp(int min, int value, int max) {
- return Math.max(min, Math.min(value, max));
- }
-
- private static void safeMunmap(long address, long mapSize) {
- try {
- Os.munmap(address, mapSize);
- } catch (ErrnoException ex) {
- Slog.w(TAG, "ignoring error in unmap", ex);
- }
- }
-
- /**
- * Close FD, swallowing irrelevant errors.
- */
- private static void safeClose(@Nullable FileDescriptor fd) {
- if (fd != null && fd.valid()) {
- try {
- Os.close(fd);
- } catch (ErrnoException ex) {
- // Swallow the exception: non-EBADF errors in close(2)
- // indicate deferred paging write errors, which we
- // don't care about here. The underlying file
- // descriptor is always closed.
- if (ex.errno == OsConstants.EBADF) {
- throw new AssertionError(ex);
- }
- }
- }
- }
-
- /**
- * Close closeable thing, swallowing errors.
- */
- private static void safeClose(@Nullable Closeable thing) {
- if (thing != null) {
- try {
- thing.close();
- } catch (IOException ex) {
- Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
- }
- }
+ return unpinnedBytes;
}
public List<PinnedFileStat> getPinnerStats() {
ArrayList<PinnedFileStat> stats = new ArrayList<>();
Collection<PinnedFile> pinnedFiles;
- synchronized(this) {
+ synchronized (this) {
pinnedFiles = mPinnedFiles.values();
}
for (PinnedFile pf : pinnedFiles) {
@@ -1355,8 +1285,8 @@ public final class PinnerService extends SystemService {
stats.add(stat);
}
if (mCurrentlyPinnedAnonSize > 0) {
- stats.add(new PinnedFileStat(ANON_REGION_STAT_NAME,
- mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
+ stats.add(new PinnedFileStat(
+ ANON_REGION_STAT_NAME, mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
}
return stats;
}
@@ -1364,71 +1294,128 @@ public final class PinnerService extends SystemService {
public final class BinderService extends IPinnerService.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw))
+ return;
HashSet<PinnedFile> shownPins = new HashSet<>();
- HashSet<String> groups = new HashSet<>();
- final int bytesPerMB = 1024 * 1024;
+ HashSet<String> shownGroups = new HashSet<>();
+ HashSet<String> groupsToPrint = new HashSet<>();
+ final double bytesPerMB = 1024 * 1024;
+ pw.format("Pinner Configs:\n");
+ pw.format(" Total Pinner quota: %d%% of total device memory\n",
+ mConfiguredMaxPinnedMemoryPercentage);
+ pw.format(" Maximum Pinner quota: %d bytes (%.2f MB)\n", mConfiguredMaxPinnedMemory,
+ mConfiguredMaxPinnedMemory / bytesPerMB);
+ pw.format(" Max Home App Pin Bytes (without deps): %d\n", mConfiguredHomePinBytes);
+ pw.format(" Max Assistant App Pin Bytes (without deps): %d\n",
+ mConfiguredAssistantPinBytes);
+ pw.format(
+ " Max Camera App Pin Bytes (without deps): %d\n", mConfiguredCameraPinBytes);
+ pw.format("\nPinned Files:\n");
synchronized (PinnerService.this) {
long totalSize = 0;
+
+ // We print apps separately from regular pins as they contain extra information that
+ // other pins do not.
for (int key : mPinnedApps.keySet()) {
PinnedApp app = mPinnedApps.get(key);
pw.print(getNameForKey(key));
- pw.print(" uid="); pw.print(app.uid);
- pw.print(" active="); pw.print(app.active);
+ pw.print(" uid=");
+ pw.print(app.uid);
+ pw.print(" active=");
+ pw.print(app.active);
+
+ if (!app.mFiles.isEmpty()) {
+ shownGroups.add(app.mFiles.getFirst().groupName);
+ }
pw.println();
+ long bytesPinnedForApp = 0;
+ long bytesPinnedForAppDeps = 0;
for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
pw.print(" ");
- pw.format("%s pinned:%d bytes (%d MB) pinlist:%b\n", pf.fileName,
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b\n", pf.fileName,
pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist);
totalSize += pf.bytesPinned;
+ bytesPinnedForApp += pf.bytesPinned;
shownPins.add(pf);
for (PinnedFile dep : pf.pinnedDeps) {
pw.print(" ");
- pw.format("%s pinned:%d bytes (%d MB) pinlist:%b (Dependency)\n", dep.fileName,
- dep.bytesPinned, dep.bytesPinned / bytesPerMB, dep.used_pinlist);
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
+ dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
+ dep.used_pinlist);
totalSize += dep.bytesPinned;
+ bytesPinnedForAppDeps += dep.bytesPinned;
shownPins.add(dep);
}
}
+ long bytesPinnedForAppAndDeps = bytesPinnedForApp + bytesPinnedForAppDeps;
+ pw.format("Total Pinned = %d (%.2f MB) [App=%d (%.2f MB), "
+ + "Dependencies=%d (%.2f MB)]\n\n",
+ bytesPinnedForAppAndDeps, bytesPinnedForAppAndDeps / bytesPerMB,
+ bytesPinnedForApp, bytesPinnedForApp / bytesPerMB,
+ bytesPinnedForAppDeps, bytesPinnedForAppDeps / bytesPerMB);
}
pw.println();
for (PinnedFile pinnedFile : mPinnedFiles.values()) {
- if (!groups.contains(pinnedFile.groupName)) {
- groups.add(pinnedFile.groupName);
+ if (!groupsToPrint.contains(pinnedFile.groupName)
+ && !shownGroups.contains(pinnedFile.groupName)) {
+ groupsToPrint.add(pinnedFile.groupName);
}
}
- boolean firstPinInGroup = true;
- for (String group : groups) {
+
+ // Print all the non app groups.
+ for (String group : groupsToPrint) {
List<PinnedFile> groupPins = getAllPinsForGroup(group);
+ pw.print("\nGroup:" + group);
+ long bytesPinnedForGroupNoDeps = 0;
+ long bytesPinnedForGroupDeps = 0;
+ pw.println();
for (PinnedFile pinnedFile : groupPins) {
if (shownPins.contains(pinnedFile)) {
- // Already showed in the dump and accounted for, skip.
+ // Already displayed and accounted for, skip.
continue;
}
- if (firstPinInGroup) {
- firstPinInGroup = false;
- // Ensure we only print when there are pins for groups not yet shown
- // in the pinned app section.
- pw.print("Group:" + group);
- pw.println();
- }
- pw.format(" %s pinned:%d bytes (%d MB) pinlist:%b\n", pinnedFile.fileName,
- pinnedFile.bytesPinned, pinnedFile.bytesPinned / bytesPerMB,
- pinnedFile.used_pinlist);
+ pw.format(" %s pinned: %d bytes (%.2f MB) pinlist:%b\n",
+ pinnedFile.fileName, pinnedFile.bytesPinned,
+ pinnedFile.bytesPinned / bytesPerMB, pinnedFile.used_pinlist);
totalSize += pinnedFile.bytesPinned;
+ bytesPinnedForGroupNoDeps += pinnedFile.bytesPinned;
+ shownPins.add(pinnedFile);
+ for (PinnedFile dep : pinnedFile.pinnedDeps) {
+ if (shownPins.contains(dep)) {
+ // Already displayed and accounted for, skip.
+ continue;
+ }
+ pw.print(" ");
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
+ dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
+ dep.used_pinlist);
+ totalSize += dep.bytesPinned;
+ bytesPinnedForGroupDeps += dep.bytesPinned;
+ shownPins.add(dep);
+ }
}
+ long bytesPinnedForGroup = bytesPinnedForGroupNoDeps + bytesPinnedForGroupDeps;
+ pw.format("Total Pinned = %d (%.2f MB) [Main=%d (%.2f MB), "
+ + "Dependencies=%d (%.2f MB)]\n\n",
+ bytesPinnedForGroup, bytesPinnedForGroup / bytesPerMB,
+ bytesPinnedForGroupNoDeps, bytesPinnedForGroupNoDeps / bytesPerMB,
+ bytesPinnedForGroupDeps, bytesPinnedForGroupDeps / bytesPerMB);
}
pw.println();
if (mPinAnonAddress != 0) {
- pw.format("Pinned anon region: %d (%d MB)\n", mCurrentlyPinnedAnonSize, mCurrentlyPinnedAnonSize / bytesPerMB);
+ pw.format("Pinned anon region: %d (%.2f MB)\n", mCurrentlyPinnedAnonSize,
+ mCurrentlyPinnedAnonSize / bytesPerMB);
totalSize += mCurrentlyPinnedAnonSize;
}
- pw.format("Total pinned: %s bytes (%s MB)\n", totalSize, totalSize / bytesPerMB);
+ pw.format("Total pinned: %d bytes (%.2f MB)\n", totalSize, totalSize / bytesPerMB);
+ pw.format("Available Pinner quota: %d bytes (%.2f MB)\n", getAvailableGlobalQuota(),
+ getAvailableGlobalQuota() / bytesPerMB);
pw.println();
if (!mPendingRepin.isEmpty()) {
pw.print("Pending repin: ");
for (int key : mPendingRepin.values()) {
- pw.print(getNameForKey(key)); pw.print(' ');
+ pw.print(getNameForKey(key));
+ pw.print(' ');
}
pw.println();
}
@@ -1462,8 +1449,9 @@ public final class PinnerService extends SystemService {
repin();
break;
default:
- printError(out, String.format(
- "Unknown pinner command: %s. Supported commands: repin", command));
+ printError(out,
+ String.format("Unknown pinner command: %s. Supported commands: repin",
+ command));
resultReceiver.send(-1, null);
return;
}
@@ -1479,46 +1467,6 @@ public final class PinnerService extends SystemService {
}
}
- @VisibleForTesting
- public static final class PinnedFile implements AutoCloseable {
- private long mAddress;
- final int mapSize;
- final String fileName;
- final int bytesPinned;
-
- // Whether this file was pinned using a pinlist
- boolean used_pinlist;
-
- // User defined group name for pinner accounting
- String groupName = "";
- ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
-
- PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
- mAddress = address;
- this.mapSize = mapSize;
- this.fileName = fileName;
- this.bytesPinned = bytesPinned;
- }
-
- @Override
- public void close() {
- if (mAddress >= 0) {
- safeMunmap(mAddress, mapSize);
- mAddress = -1;
- }
- for (PinnedFile dep : pinnedDeps) {
- if (dep != null) {
- dep.close();
- }
- }
- }
-
- @Override
- public void finalize() {
- close();
- }
- }
-
final static class PinRange {
int start;
int length;
@@ -1528,7 +1476,6 @@ public final class PinnerService extends SystemService {
* Represents an app that was pinned.
*/
private final class PinnedApp {
-
/**
* The uid of the package being pinned. This stays constant while the package stays
* installed.
@@ -1557,11 +1504,9 @@ public final class PinnerService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case PIN_ONSTART_MSG:
- {
+ case PIN_ONSTART_MSG: {
handlePinOnStart();
- }
- break;
+ } break;
default:
super.handleMessage(msg);
diff --git a/services/core/java/com/android/server/pinner/PinnerUtils.java b/services/core/java/com/android/server/pinner/PinnerUtils.java
new file mode 100644
index 000000000000..a836a83dedab
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinnerUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import android.annotation.Nullable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/* package */ final class PinnerUtils {
+ private static final String TAG = "PinnerUtils";
+
+ public static long clamp(long min, long value, long max) {
+ return Math.max(min, Math.min(value, max));
+ }
+
+ public static void safeMunmap(long address, long mapSize) {
+ try {
+ Os.munmap(address, mapSize);
+ } catch (ErrnoException ex) {
+ Slog.w(TAG, "ignoring error in unmap", ex);
+ }
+ }
+
+ /**
+ * Close FD, swallowing irrelevant errors.
+ */
+ public static void safeClose(@Nullable FileDescriptor fd) {
+ if (fd != null && fd.valid()) {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ex) {
+ // Swallow the exception: non-EBADF errors in close(2)
+ // indicate deferred paging write errors, which we
+ // don't care about here. The underlying file
+ // descriptor is always closed.
+ if (ex.errno == OsConstants.EBADF) {
+ throw new AssertionError(ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Close closeable thing, swallowing errors.
+ */
+ public static void safeClose(@Nullable Closeable thing) {
+ if (thing != null) {
+ try {
+ thing.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
index 155361837071..bc0fc2b0b7d9 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -33,8 +33,10 @@ import com.android.server.ServiceThread;
public class BackgroundInstallControlCallbackHelper {
- @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
- @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
+ public static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+ public static final String FLAGGED_USER_ID_KEY = "userId";
+ public static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+
private static final String TAG = "BackgroundInstallControlCallbackHelper";
private final Handler mHandler;
@@ -74,10 +76,14 @@ public class BackgroundInstallControlCallbackHelper {
* Invokes all registered callbacks Callbacks are processed through user provided-threads and
* parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent
*/
- public void notifyAllCallbacks(int userId, String packageName) {
+ public void notifyAllCallbacks(
+ int userId,
+ String packageName,
+ @BackgroundInstallControlService.InstallEventType int installEventType) {
Bundle extras = new Bundle();
extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName);
extras.putInt(FLAGGED_USER_ID_KEY, userId);
+ extras.putInt(INSTALL_EVENT_TYPE_KEY, installEventType);
synchronized (mCallbacks) {
mHandler.post(
() ->
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index b6daed121057..d538bb876b64 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.Flags;
@@ -64,6 +65,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -76,11 +79,23 @@ import java.util.TreeSet;
* @hide
*/
public class BackgroundInstallControlService extends SystemService {
+ public static final int INSTALL_EVENT_TYPE_UNKNOWN = 0;
+ public static final int INSTALL_EVENT_TYPE_INSTALL = 1;
+ public static final int INSTALL_EVENT_TYPE_UNINSTALL = 2;
+
+ @IntDef(
+ value = {
+ INSTALL_EVENT_TYPE_UNKNOWN,
+ INSTALL_EVENT_TYPE_INSTALL,
+ INSTALL_EVENT_TYPE_UNINSTALL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallEventType {}
+
private static final String TAG = "BackgroundInstallControlService";
private static final String DISK_FILE_NAME = "states";
private static final String DISK_DIR_NAME = "bic";
-
private static final String ENFORCE_PERMISSION_ERROR_MSG =
"User is not permitted to call service: ";
@@ -250,6 +265,7 @@ public class BackgroundInstallControlService extends SystemService {
@Override
public void handleMessage(Message msg) {
+ Slog.d(TAG, "Package event received: " + msg.what);
switch (msg.what) {
case MSG_USAGE_EVENT_RECEIVED:
mService.handleUsageEvent(
@@ -311,9 +327,11 @@ public class BackgroundInstallControlService extends SystemService {
return;
}
+ Slog.d(TAG, "handlePackageAdd: adding " + packageName + " from "
+ + userId + " and notifying callbacks");
initBackgroundInstalledPackages();
mBackgroundInstalledPackages.add(userId, packageName);
- mCallbackHelper.notifyAllCallbacks(userId, packageName);
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_INSTALL);
writeBackgroundInstalledPackagesToDisk();
}
@@ -349,7 +367,11 @@ public class BackgroundInstallControlService extends SystemService {
// ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
// addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
- return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
+ if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
+ Slog.d(TAG, "handlePackageAdd: is installed by ADB, skipping");
+ return true;
+ }
+ return false;
}
private boolean wasForegroundInstallation(
@@ -389,6 +411,10 @@ public class BackgroundInstallControlService extends SystemService {
void handlePackageRemove(String packageName, int userId) {
initBackgroundInstalledPackages();
+ if (mBackgroundInstalledPackages.contains(userId, packageName)) {
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
+ }
+ Slog.d(TAG, "handlePackageRemove: removing " + packageName + " from " + userId);
mBackgroundInstalledPackages.remove(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
}
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index 49a6ffde6783..77572e018dda 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -34,16 +34,19 @@ import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.audiopolicy.AudioPolicy;
+import android.multiuser.Flags;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
+import java.util.Set;
public class BackgroundUserSoundNotifier {
@@ -55,6 +58,8 @@ public class BackgroundUserSoundNotifier {
private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER";
private static final String ACTION_DISMISS_NOTIFICATION =
"com.android.server.ACTION_DISMISS_NOTIFICATION";
+ private static final String EXTRA_NOTIFICATION_CLIENT_UID =
+ "com.android.server.EXTRA_CLIENT_UID";
/**
* The clientUid from the AudioFocusInfo of the background user,
* for which an active notification is currently displayed.
@@ -63,6 +68,10 @@ public class BackgroundUserSoundNotifier {
*/
@VisibleForTesting
int mNotificationClientUid = -1;
+ /**
+ * UIDs of audio focus infos with active notifications.
+ */
+ Set<Integer> mNotificationClientUids = new ArraySet<>();
@VisibleForTesting
AudioPolicy mFocusControlAudioPolicy;
@VisibleForTesting
@@ -101,7 +110,7 @@ public class BackgroundUserSoundNotifier {
ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class);
registerReceiver(am);
- mBgUserListener = new BackgroundUserListener(mSystemUserContext);
+ mBgUserListener = new BackgroundUserListener();
AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext);
focusControlPolicyBuilder.setLooper(Looper.getMainLooper());
@@ -119,26 +128,16 @@ public class BackgroundUserSoundNotifier {
final class BackgroundUserListener extends AudioPolicy.AudioPolicyFocusListener {
- Context mSystemContext;
-
- BackgroundUserListener(Context systemContext) {
- mSystemContext = systemContext;
- }
-
- @SuppressLint("MissingPermission")
public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
try {
- BackgroundUserSoundNotifier.this.notifyForegroundUserAboutSoundIfNecessary(afi,
- mSystemContext.createContextAsUser(
- UserHandle.of(ActivityManager.getCurrentUser()), 0));
+ BackgroundUserSoundNotifier.this.notifyForegroundUserAboutSoundIfNecessary(afi);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
- @SuppressLint("MissingPermission")
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
- BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary();
+ BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi.getClientUid());
}
}
@@ -157,26 +156,46 @@ public class BackgroundUserSoundNotifier {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
- if (mNotificationClientUid == -1) {
- return;
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ if (!intent.hasExtra(EXTRA_NOTIFICATION_CLIENT_UID)) {
+ return;
+ }
+ } else {
+ if (mNotificationClientUid == -1) {
+ return;
+ }
+ }
+
+ int clientUid;
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ clientUid = intent.getIntExtra(EXTRA_NOTIFICATION_CLIENT_UID, -1);
+ } else {
+ clientUid = mNotificationClientUid;
}
- dismissNotification();
+ dismissNotification(clientUid);
if (DEBUG) {
final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
final String action = intent.getAction().substring(actionIndex);
Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
+ ActivityManager.getCurrentUser() + " for alarm on user "
- + UserHandle.getUserHandleForUid(mNotificationClientUid));
+ + UserHandle.getUserHandleForUid(clientUid).getIdentifier());
}
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
- muteAlarmSounds(mSystemUserContext);
+ muteAlarmSounds(clientUid);
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
- activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
+ int userId = UserHandle.getUserId(clientUid);
+ if (mUserManager.isProfile(userId)) {
+ userId = mUserManager.getProfileParent(userId).id;
+ }
+ activityManager.switchUser(userId);
+ }
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mNotificationClientUids.remove(clientUid);
+ } else {
+ mNotificationClientUid = -1;
}
-
- mNotificationClientUid = -1;
}
};
@@ -193,17 +212,17 @@ public class BackgroundUserSoundNotifier {
*/
@SuppressLint("MissingPermission")
@VisibleForTesting
- void muteAlarmSounds(Context context) {
- AudioManager audioManager = context.getSystemService(AudioManager.class);
+ void muteAlarmSounds(int notificationClientUid) {
+ AudioManager audioManager = mSystemUserContext.getSystemService(AudioManager.class);
if (audioManager != null) {
for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) {
- if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) {
+ if (apc.getClientUid() == notificationClientUid && apc.getPlayerProxy() != null) {
apc.getPlayerProxy().stop();
}
}
}
- AudioFocusInfo currentAfi = getAudioFocusInfoForNotification();
+ AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(notificationClientUid);
if (currentAfi != null) {
mFocusControlAudioPolicy.sendFocusLossAndUpdate(currentAfi);
}
@@ -212,16 +231,23 @@ public class BackgroundUserSoundNotifier {
/**
* Check if sound is coming from background user and show notification is required.
*/
+ @SuppressLint("MissingPermission")
@VisibleForTesting
- void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext)
- throws RemoteException {
+ void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi) throws RemoteException {
+ if (afi == null) {
+ return;
+ }
+ Context foregroundContext = mSystemUserContext.createContextAsUser(
+ UserHandle.of(ActivityManager.getCurrentUser()), 0);
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
- UserInfo userInfo = mUserManager.getUserInfo(userId);
+ UserInfo userInfo = mUserManager.isProfile(userId) ? mUserManager.getProfileParent(userId) :
+ mUserManager.getUserInfo(userId);
+ ActivityManager activityManager = foregroundContext.getSystemService(ActivityManager.class);
// Only show notification if the sound is coming from background user and the notification
- // is not already shown.
- if (userInfo != null && userId != foregroundContext.getUserId()
- && mNotificationClientUid == -1) {
+ // for this UID is not already shown.
+ if (userInfo != null && !activityManager.isProfileForeground(userInfo.getUserHandle())
+ && !isNotificationShown(afi.getClientUid())) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
if (DEBUG) {
@@ -229,11 +255,14 @@ public class BackgroundUserSoundNotifier {
+ ", displaying notification for current user "
+ foregroundContext.getUserId());
}
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mNotificationClientUids.add(afi.getClientUid());
+ } else {
+ mNotificationClientUid = afi.getClientUid();
+ }
- mNotificationClientUid = afi.getClientUid();
-
- mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid,
- createNotification(userInfo.name, foregroundContext),
+ mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
+ createNotification(userInfo.name, foregroundContext, afi.getClientUid()),
foregroundContext.getUser());
}
}
@@ -245,15 +274,22 @@ public class BackgroundUserSoundNotifier {
* focus ownership.
*/
@VisibleForTesting
- void dismissNotificationIfNecessary() {
- if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) {
+ void dismissNotificationIfNecessary(int notificationClientUid) {
+
+ if (getAudioFocusInfoForNotification(notificationClientUid) == null
+ && isNotificationShown(notificationClientUid)) {
if (DEBUG) {
Log.d(LOG_TAG, "Alarm ringing on background user "
- + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier()
+ + UserHandle.getUserHandleForUid(notificationClientUid).getIdentifier()
+ " left focus stack, dismissing notification");
}
- dismissNotification();
- mNotificationClientUid = -1;
+ dismissNotification(notificationClientUid);
+
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mNotificationClientUids.remove(notificationClientUid);
+ } else {
+ mNotificationClientUid = -1;
+ }
}
}
@@ -262,8 +298,8 @@ public class BackgroundUserSoundNotifier {
* shown.
*/
@SuppressLint("MissingPermission")
- private void dismissNotification() {
- mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL);
+ private void dismissNotification(int notificationClientUid) {
+ mNotificationManager.cancelAsUser(LOG_TAG, notificationClientUid, UserHandle.ALL);
}
/**
@@ -272,11 +308,11 @@ public class BackgroundUserSoundNotifier {
@SuppressLint("MissingPermission")
@VisibleForTesting
@Nullable
- AudioFocusInfo getAudioFocusInfoForNotification() {
- if (mNotificationClientUid >= 0) {
+ AudioFocusInfo getAudioFocusInfoForNotification(int notificationClientUid) {
+ if (notificationClientUid >= 0) {
List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack();
for (int i = stack.size() - 1; i >= 0; i--) {
- if (stack.get(i).getClientUid() == mNotificationClientUid) {
+ if (stack.get(i).getClientUid() == notificationClientUid) {
return stack.get(i);
}
}
@@ -284,22 +320,24 @@ public class BackgroundUserSoundNotifier {
return null;
}
- private PendingIntent createPendingIntent(String intentAction) {
+ private PendingIntent createPendingIntent(String intentAction, int notificationClientUid) {
final Intent intent = new Intent(intentAction);
- PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- return resultPI;
+ intent.putExtra(EXTRA_NOTIFICATION_CLIENT_UID, notificationClientUid);
+ return PendingIntent.getBroadcast(mSystemUserContext, notificationClientUid, intent,
+ PendingIntent.FLAG_IMMUTABLE);
}
+ @SuppressLint("MissingPermission")
@VisibleForTesting
- Notification createNotification(String userName, Context fgContext) {
+ Notification createNotification(String userName, Context fgContext, int notificationClientUid) {
final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm,
userName);
final int icon = R.drawable.ic_audio_alarm;
- PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND);
- PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER);
- PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION);
+ PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND, notificationClientUid);
+ PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER, notificationClientUid);
+ PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION,
+ notificationClientUid);
final Notification.Action mute = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_mute),
@@ -331,4 +369,12 @@ public class BackgroundUserSoundNotifier {
return notificationBuilder.build();
}
+
+ private boolean isNotificationShown(int notificationClientUid) {
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ return mNotificationClientUids.contains(notificationClientUid);
+ } else {
+ return mNotificationClientUid != -1;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 369029adac59..67e10953ac91 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -53,17 +53,22 @@ import android.os.Handler;
import android.os.PowerExemptionManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.DeviceConfig;
import android.stats.storage.StorageEnums;
+import android.text.TextUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.pm.pkg.AndroidPackage;
@@ -74,17 +79,15 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.BiFunction;
+import java.util.function.Supplier;
/**
* Helper class to send broadcasts for various situations.
*/
public final class BroadcastHelper {
private static final boolean DEBUG_BROADCASTS = false;
- /**
- * Permissions required in order to receive instant application lifecycle broadcasts.
- */
- private static final String[] INSTANT_APP_BROADCAST_PERMISSION =
- new String[]{android.Manifest.permission.ACCESS_INSTANT_APPS};
+ private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
+ "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
private final UserManagerInternal mUmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -115,7 +118,7 @@ public final class BroadcastHelper {
SparseArray<int[]> broadcastAllowList = new SparseArray<>();
broadcastAllowList.put(userId, visibilityAllowList);
broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList,
- filterExtrasForReceiver, bOptions);
+ filterExtrasForReceiver, bOptions, null /* requiredPermissions */);
}
void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
@@ -123,7 +126,7 @@ public final class BroadcastHelper {
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @Nullable Bundle bOptions) {
+ @Nullable Bundle bOptions, @Nullable String[] requiredPermissions) {
try {
final IActivityManager am = ActivityManager.getService();
if (am == null) return;
@@ -137,12 +140,12 @@ public final class BroadcastHelper {
if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
- filterExtrasForReceiver, bOptions);
+ filterExtrasForReceiver, bOptions, requiredPermissions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true /* isInstantApp */, null,
- null /* filterExtrasForReceiver */, bOptions);
+ instantUserIds, true /* isInstantApp */, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, bOptions, requiredPermissions);
}
} catch (RemoteException ex) {
}
@@ -166,7 +169,8 @@ public final class BroadcastHelper {
boolean isInstantApp,
@Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @Nullable Bundle bOptions) {
+ @Nullable Bundle bOptions,
+ @Nullable String[] requiredPermissions) {
for (int userId : userIds) {
final Intent intent = new Intent(action,
pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
@@ -189,17 +193,18 @@ public final class BroadcastHelper {
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList,
- filterExtrasForReceiver, bOptions);
+ filterExtrasForReceiver, bOptions, requiredPermissions);
}
}
-
private void broadcastIntent(Intent intent, IIntentReceiver finishedReceiver,
boolean isInstantApp, int userId, @Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @Nullable Bundle bOptions) {
- final String[] requiredPermissions =
- isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null;
+ @Nullable Bundle bOptions, @Nullable String[] requiredPermissions) {
+ if (isInstantApp) {
+ requiredPermissions = ArrayUtils.appendElement(String.class, requiredPermissions,
+ android.Manifest.permission.ACCESS_INSTANT_APPS);
+ }
if (DEBUG_BROADCASTS) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
@@ -213,7 +218,7 @@ public final class BroadcastHelper {
filterExtrasForReceiver, bOptions);
}
- void sendResourcesChangedBroadcast(@NonNull Computer snapshot,
+ void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotSupplier,
boolean mediaStatus,
boolean replacing,
@NonNull String[] pkgNames,
@@ -233,8 +238,8 @@ public final class BroadcastHelper {
null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
null /* instantUserIds */, null /* broadcastAllowList */,
(callingUid, intentExtras) -> filterExtrasChangedPackageList(
- snapshot, callingUid, intentExtras),
- null /* bOptions */);
+ snapshotSupplier, callingUid, intentExtras),
+ null /* bOptions */, null /* requiredPermissions */);
}
/**
@@ -294,14 +299,152 @@ public final class BroadcastHelper {
return bOptions;
}
- private void sendPackageChangedBroadcast(@NonNull String packageName,
- boolean dontKillApp,
- @NonNull ArrayList<String> componentNames,
- int packageUid,
- @Nullable String reason,
- @Nullable int[] userIds,
- @Nullable int[] instantUserIds,
- @Nullable SparseArray<int[]> broadcastAllowList) {
+ private ArrayList<String> getAllNotExportedComponents(@NonNull AndroidPackage pkg,
+ @NonNull ArrayList<String> inputComponentNames) {
+ final ArrayList<String> outputNotExportedComponentNames = new ArrayList<>();
+ int remainingComponentCount = inputComponentNames.size();
+ for (ParsedActivity component : pkg.getReceivers()) {
+ if (inputComponentNames.contains(component.getClassName())) {
+ if (!component.isExported()) {
+ outputNotExportedComponentNames.add(component.getClassName());
+ }
+ remainingComponentCount--;
+ if (remainingComponentCount <= 0) {
+ return outputNotExportedComponentNames;
+ }
+ }
+ }
+ for (ParsedProvider component : pkg.getProviders()) {
+ if (inputComponentNames.contains(component.getClassName())) {
+ if (!component.isExported()) {
+ outputNotExportedComponentNames.add(component.getClassName());
+ }
+ remainingComponentCount--;
+ if (remainingComponentCount <= 0) {
+ return outputNotExportedComponentNames;
+ }
+ }
+ }
+ for (ParsedService component : pkg.getServices()) {
+ if (inputComponentNames.contains(component.getClassName())) {
+ if (!component.isExported()) {
+ outputNotExportedComponentNames.add(component.getClassName());
+ }
+ remainingComponentCount--;
+ if (remainingComponentCount <= 0) {
+ return outputNotExportedComponentNames;
+ }
+ }
+ }
+ for (ParsedActivity component : pkg.getActivities()) {
+ if (inputComponentNames.contains(component.getClassName())) {
+ if (!component.isExported()) {
+ outputNotExportedComponentNames.add(component.getClassName());
+ }
+ remainingComponentCount--;
+ if (remainingComponentCount <= 0) {
+ return outputNotExportedComponentNames;
+ }
+ }
+ }
+ return outputNotExportedComponentNames;
+ }
+
+ private void sendPackageChangedBroadcastInternal(@NonNull String packageName,
+ boolean dontKillApp,
+ @NonNull ArrayList<String> componentNames,
+ int packageUid,
+ @Nullable String reason,
+ @Nullable int[] userIds,
+ @Nullable int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList,
+ @NonNull AndroidPackage pkg,
+ @NonNull String[] sharedUidPackages,
+ @NonNull String reasonForTrace) {
+ final boolean isForWholeApp = componentNames.contains(packageName);
+ if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
+ tracePackageChangedBroadcastEvent(
+ android.content.pm.Flags.reduceBroadcastsForComponentStateChanges(),
+ reasonForTrace, "all" /* targetName */, "whole" /* targetComponent */,
+ componentNames.size());
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
+ packageUid, reason, userIds, instantUserIds, broadcastAllowList,
+ null /* targetPackageName */, null /* requiredPermissions */);
+ return;
+ }
+ // Currently only these four components of activity, receiver, provider and service are
+ // considered to send only the broadcast to the system and the application itself when the
+ // component is not exported. In order to avoid losing to send the broadcast for other
+ // components, it gets the not exported components for these four components of activity,
+ // receiver, provider and service and the others are considered the exported components.
+ final ArrayList<String> notExportedComponentNames = getAllNotExportedComponents(pkg,
+ componentNames);
+ final ArrayList<String> exportedComponentNames = (ArrayList<String>) componentNames.clone();
+ exportedComponentNames.removeAll(notExportedComponentNames);
+
+ if (!notExportedComponentNames.isEmpty()) {
+ // Limit sending of the PACKAGE_CHANGED broadcast to only the system, the application
+ // itself and applications with the same UID when the component is not exported.
+
+ // First, send the PACKAGE_CHANGED broadcast to the system.
+ if (!TextUtils.equals(packageName, "android")) {
+ tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace,
+ "system" /* targetName */, "notExported" /* targetComponent */,
+ notExportedComponentNames.size());
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, "android" /* targetPackageName */,
+ new String[]{
+ PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ }
+
+ // Second, send the PACKAGE_CHANGED broadcast to the application itself.
+ tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace,
+ "applicationItself" /* targetName */, "notExported" /* targetComponent */,
+ notExportedComponentNames.size());
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, packageName /* targetPackageName */,
+ null /* requiredPermissions */);
+
+ // Third, send the PACKAGE_CHANGED broadcast to the applications with the same UID.
+ for (int i = 0; i < sharedUidPackages.length; i++) {
+ final String sharedPackage = sharedUidPackages[i];
+ if (TextUtils.equals(packageName, sharedPackage)) {
+ continue;
+ }
+ tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace,
+ "sharedUidPackages" /* targetName */, "notExported" /* targetComponent */,
+ notExportedComponentNames.size());
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, sharedPackage /* targetPackageName */,
+ null /* requiredPermissions */);
+ }
+
+ }
+
+ if (!exportedComponentNames.isEmpty()) {
+ tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace,
+ "all" /* targetName */, "exported" /* targetComponent */,
+ exportedComponentNames.size());
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ exportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, null /* targetPackageName */,
+ null /* requiredPermissions */);
+ }
+ }
+
+ private void sendPackageChangedBroadcastWithPermissions(@NonNull String packageName,
+ boolean dontKillApp,
+ @NonNull ArrayList<String> componentNames,
+ int packageUid,
+ @Nullable String reason,
+ @Nullable int[] userIds,
+ @Nullable int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable String targetPackageName,
+ @Nullable String[] requiredPermissions) {
if (DEBUG_INSTALL) {
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
@@ -321,9 +464,10 @@ public final class BroadcastHelper {
// little component state change.
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
- sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
- userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
- null /* bOptions */);
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags,
+ targetPackageName, null /* finishedReceiver */, userIds, instantUserIds,
+ broadcastAllowList, null /* filterExtrasForReceiver */, null /* bOptions */,
+ requiredPermissions);
}
static void sendDeviceCustomizationReadyBroadcast() {
@@ -419,7 +563,7 @@ public final class BroadcastHelper {
});
}
- void sendPostInstallBroadcasts(@NonNull Computer snapshot,
+ void sendPostInstallBroadcasts(@NonNull Supplier<Computer> snapshotSupplier,
@NonNull InstallRequest request,
@NonNull String packageName,
@NonNull String requiredPermissionControllerPackage,
@@ -442,8 +586,8 @@ public final class BroadcastHelper {
final int[] uids = new int[]{request.getRemovedInfo().mUid};
notifyResourcesChanged(
false /* mediaStatus */, true /* replacing */, pkgNames, uids);
- sendResourcesChangedBroadcast(
- snapshot, false /* mediaStatus */, true /* replacing */, pkgNames, uids);
+ sendResourcesChangedBroadcast(snapshotSupplier,
+ false /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
sendPackageRemovedBroadcasts(
request.getRemovedInfo(), packageSender, isKillApp, false /*removedBySystem*/,
@@ -483,6 +627,7 @@ public final class BroadcastHelper {
null /* broadcastAllowList */, null);
}
+ final Computer snapshot = snapshotSupplier.get();
// Send installed broadcasts if the package is not a static shared lib.
if (staticSharedLibraryName == null) {
// Send PACKAGE_ADDED broadcast for users that see the package for the first time
@@ -607,7 +752,7 @@ public final class BroadcastHelper {
if (!isArchived) {
final String[] pkgNames = new String[]{packageName};
final int[] uids = new int[]{request.getPkg().getUid()};
- sendResourcesChangedBroadcast(snapshot,
+ sendResourcesChangedBroadcast(snapshotSupplier,
true /* mediaStatus */, true /* replacing */, pkgNames, uids);
notifyResourcesChanged(true /* mediaStatus */,
true /* replacing */, pkgNames, uids);
@@ -624,7 +769,8 @@ public final class BroadcastHelper {
sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
dontKillApp,
new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
- pkg.getUid(), null);
+ pkg.getUid(), null /* reason */,
+ "static_shared_library_changed" /* reasonForTrace */);
}
}
}
@@ -680,7 +826,8 @@ public final class BroadcastHelper {
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0, null, null, userIds, instantUserIds,
- broadcastAllowlist, null /* filterExtrasForReceiver */, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
// Send to PermissionController for all new users, even if it may not be running for some
// users
if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
@@ -688,7 +835,8 @@ public final class BroadcastHelper {
packageName, extras, 0,
mContext.getPackageManager().getPermissionControllerPackageName(),
null, userIds, instantUserIds,
- broadcastAllowlist, null /* filterExtrasForReceiver */, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
}
@@ -719,7 +867,8 @@ public final class BroadcastHelper {
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
- null /* filterExtrasForReceiver */, null);
+ null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
/**
@@ -732,8 +881,8 @@ public final class BroadcastHelper {
* access all the packages in the extras.
*/
@Nullable
- private static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
- @NonNull Bundle extras) {
+ private static Bundle filterExtrasChangedPackageList(
+ @NonNull Supplier<Computer> snapshotSupplier, int callingUid, @NonNull Bundle extras) {
if (UserHandle.isCore(callingUid)) {
// see all
return extras;
@@ -745,6 +894,7 @@ public final class BroadcastHelper {
final int userId = extras.getInt(
Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid));
final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST);
+ final Computer snapshot = snapshotSupplier.get();
final Pair<String[], int[]> filteredPkgs =
filterPackages(snapshot, pkgs, uids, callingUid, userId);
if (ArrayUtils.isEmpty(filteredPkgs.first)) {
@@ -811,10 +961,11 @@ public final class BroadcastHelper {
boolean dontKillApp,
@NonNull ArrayList<String> componentNames,
int packageUid,
- @NonNull String reason) {
+ @NonNull String reason,
+ @NonNull String reasonForTrace) {
PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
Process.SYSTEM_UID);
- if (setting == null) {
+ if (setting == null || setting.getPkg() == null) {
return;
}
final int userId = UserHandle.getUserId(packageUid);
@@ -824,9 +975,12 @@ public final class BroadcastHelper {
final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
final SparseArray<int[]> broadcastAllowList =
isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
- mHandler.post(() -> sendPackageChangedBroadcast(
+ final String[] sharedUserPackages =
+ snapshot.getSharedUserPackagesForPackage(packageName, userId);
+ mHandler.post(() -> sendPackageChangedBroadcastInternal(
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
- instantUserIds, broadcastAllowList));
+ instantUserIds, broadcastAllowList, setting.getPkg(),
+ sharedUserPackages, reasonForTrace));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
}
@@ -843,7 +997,7 @@ public final class BroadcastHelper {
@Nullable Bundle bOptions) {
mHandler.post(() -> sendPackageBroadcast(action, pkg, extras, flags,
targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
- null /* filterExtrasForReceiver */, bOptions));
+ null /* filterExtrasForReceiver */, bOptions, null /* requiredPermissions */));
if (targetPkg == null) {
// For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
// many times to different targets, e.g. installer app, permission controller, other
@@ -991,7 +1145,7 @@ public final class BroadcastHelper {
* @param uidList The uids of packages which have suspension changes.
* @param userId The user where packages reside.
*/
- void sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Computer snapshot,
+ void sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Supplier<Computer> snapshotSupplier,
@NonNull String intent,
@NonNull String[] pkgList,
@NonNull int[] uidList,
@@ -1009,17 +1163,17 @@ public final class BroadcastHelper {
.toBundle();
BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver =
(callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
- snapshot, callingUid, intentExtras);
+ snapshotSupplier, callingUid, intentExtras);
mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
extras, flags, null /* targetPkg */, null /* finishedReceiver */,
new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
filterExtrasForReceiver,
- options));
+ options, null /* requiredPermissions */));
notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver);
}
- void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
+ void sendMyPackageSuspendedOrUnsuspended(@NonNull Supplier<Computer> snapshotSupplier,
@NonNull String[] affectedPackages,
boolean suspended,
int userId) {
@@ -1034,6 +1188,7 @@ public final class BroadcastHelper {
return;
}
final int[] targetUserIds = new int[] {userId};
+ final Computer snapshot = snapshotSupplier.get();
for (String packageName : affectedPackages) {
final Bundle appExtras = suspended
? SuspendPackageHelper.getSuspendedPackageAppExtras(
@@ -1046,9 +1201,12 @@ public final class BroadcastHelper {
} else {
intentExtras = null;
}
- doSendBroadcast(action, null, intentExtras,
- Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
- targetUserIds, false, null, null, null);
+ doSendBroadcast(action, null /* pkg */, intentExtras,
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName,
+ null /* finishedReceiver */,
+ targetUserIds, false /* isInstantApp */, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
});
}
@@ -1060,7 +1218,7 @@ public final class BroadcastHelper {
* @param uidList The uids of packages which have suspension changes.
* @param userId The user where packages reside.
*/
- void sendDistractingPackagesChanged(@NonNull Computer snapshot,
+ void sendDistractingPackagesChanged(@NonNull Supplier<Computer> snapshotSupplier,
@NonNull String[] pkgList,
@NonNull int[] uidList,
int userId,
@@ -1076,11 +1234,11 @@ public final class BroadcastHelper {
null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
null /* broadcastAllowList */,
(callingUid, intentExtras) -> filterExtrasChangedPackageList(
- snapshot, callingUid, intentExtras),
- null /* bOptions */));
+ snapshotSupplier, callingUid, intentExtras),
+ null /* bOptions */, null /* requiredPermissions */));
}
- void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot,
+ void sendResourcesChangedBroadcastAndNotify(@NonNull Supplier<Computer> snapshotSupplier,
boolean mediaStatus,
boolean replacing,
@NonNull ArrayList<AndroidPackage> packages) {
@@ -1092,7 +1250,7 @@ public final class BroadcastHelper {
packageNames[i] = pkg.getPackageName();
packageUids[i] = pkg.getUid();
}
- sendResourcesChangedBroadcast(snapshot, mediaStatus,
+ sendResourcesChangedBroadcast(snapshotSupplier, mediaStatus,
replacing, packageNames, packageUids);
notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids);
}
@@ -1115,4 +1273,22 @@ public final class BroadcastHelper {
mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames,
uids, mHandler);
}
+
+ private static void tracePackageChangedBroadcastEvent(boolean applyFlag, String reasonForTrace,
+ String targetName, String targetComponent, int componentSize) {
+
+ if (!Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ return;
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append("broadcastPackageChanged; ");
+ builder.append("af="); builder.append(applyFlag);
+ builder.append(",rft="); builder.append(reasonForTrace);
+ builder.append(",tn="); builder.append(targetName);
+ builder.append(",tc="); builder.append(targetComponent);
+ builder.append(",cs="); builder.append(componentSize);
+
+ Trace.instant(Trace.TRACE_TAG_SYSTEM_SERVER, builder.toString());
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 4665a72b0b06..be2f58dc276c 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -71,6 +71,7 @@ import android.app.ActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -137,7 +138,8 @@ import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerInternal;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerLocal;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -649,11 +651,11 @@ public class ComputerEngine implements Computer {
int userId, int callingUid, int callingPid,
boolean includeInstantApps, boolean resolveForStart) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
- enforceCrossUserOrProfilePermission(callingUid,
+ enforceCrossUserOrProfilePermission(Binder.getCallingUid(),
userId,
false /*requireFullPermission*/,
false /*checkShell*/,
- "query intent receivers");
+ "query intent services");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
@@ -2208,10 +2210,10 @@ public class ComputerEngine implements Computer {
return true;
}
boolean permissionGranted = requireFullPermission ? hasPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
: (hasPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS));
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid));
if (!permissionGranted) {
if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) {
return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission);
@@ -2768,7 +2770,8 @@ public class ComputerEngine implements Computer {
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId),
"MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission");
- } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0
+ } else if (!Flags.removeCrossUserPermissionHack()
+ && (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0
&& isCallerSystemUser
&& mUserManager.hasProfile(UserHandle.USER_SYSTEM)) {
// If the caller wants all packages and has a profile associated with it,
@@ -4668,7 +4671,7 @@ public class ComputerEngine implements Computer {
if (!forceAllowCrossUser) {
enforceCrossUserPermission(
- callingUid,
+ Binder.getCallingUid(),
userId,
false /* requireFullPermission */,
false /* checkShell */,
@@ -4751,8 +4754,14 @@ public class ComputerEngine implements Computer {
int callingUid) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId);
- final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,
- userId);
+
+ // Callers of this API may not always separate the userID and authority. Let's parse it
+ // before resolving
+ String authorityWithoutUserId = ContentProvider.getAuthorityWithoutUserId(name);
+ userId = ContentProvider.getUserIdFromAuthority(name, userId);
+
+ final ProviderInfo providerInfo = mComponentResolver.queryProvider(this,
+ authorityWithoutUserId, flags, userId);
boolean checkedGrants = false;
if (providerInfo != null) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
@@ -4766,7 +4775,7 @@ public class ComputerEngine implements Computer {
if (!checkedGrants) {
boolean enforceCrossUser = true;
- if (isAuthorityRedirectedForCloneProfile(name)) {
+ if (isAuthorityRedirectedForCloneProfile(authorityWithoutUserId)) {
final UserManagerInternal umInternal = mInjector.getUserManagerInternal();
UserInfo userInfo = umInternal.getUserInfo(UserHandle.getUserId(callingUid));
@@ -5241,7 +5250,7 @@ public class ComputerEngine implements Computer {
@Override
public int getComponentEnabledSetting(@NonNull ComponentName component, int callingUid,
@UserIdInt int userId) {
- enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, false /*requireFullPermission*/,
false /*checkShell*/, "getComponentEnabled");
return getComponentEnabledSettingInternal(component, callingUid, userId);
}
@@ -5843,10 +5852,10 @@ public class ComputerEngine implements Computer {
if (isHotword) {
return true;
}
- OnDeviceIntelligenceManagerInternal onDeviceIntelligenceManagerInternal =
- mInjector.getLocalService(OnDeviceIntelligenceManagerInternal.class);
- return onDeviceIntelligenceManagerInternal != null
- && uid == onDeviceIntelligenceManagerInternal.getInferenceServiceUid();
+ OnDeviceIntelligenceManagerLocal onDeviceIntelligenceManagerLocal =
+ LocalManagerRegistry.getManager(OnDeviceIntelligenceManagerLocal.class);
+ return onDeviceIntelligenceManagerLocal != null
+ && uid == onDeviceIntelligenceManagerLocal.getInferenceServiceUid();
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 5ac883c58f03..9c24abcef123 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -316,6 +316,8 @@ public class DefaultCrossProfileIntentFiltersUtils {
/* letsPersonalDataIntoProfile= */ true)
.addAction(MediaStore.ACTION_IMAGE_CAPTURE)
.addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+ .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE)
+ .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE_SECURE)
.addAction(MediaStore.ACTION_VIDEO_CAPTURE)
.addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
.addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
@@ -438,6 +440,8 @@ public class DefaultCrossProfileIntentFiltersUtils {
/* letsPersonalDataIntoProfile= */ false)
.addAction(MediaStore.ACTION_IMAGE_CAPTURE)
.addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+ .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE)
+ .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE_SECURE)
.addAction(MediaStore.ACTION_VIDEO_CAPTURE)
.addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
.addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 8398ffc75f0d..f6e518a4fed7 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -397,7 +397,7 @@ final class DeletePackageHelper {
try {
executeDeletePackageLIF(action, packageName, deleteCodeAndResources,
- allUserHandles, writeSettings);
+ allUserHandles, writeSettings, /* keepArtProfile= */ false);
} catch (SystemDeleteException e) {
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: system deletion failure", e);
return false;
@@ -433,11 +433,11 @@ final class DeletePackageHelper {
}
public void executeDeletePackage(DeletePackageAction action, String packageName,
- boolean deleteCodeAndResources, @NonNull int[] allUserHandles, boolean writeSettings)
- throws SystemDeleteException {
+ boolean deleteCodeAndResources, @NonNull int[] allUserHandles, boolean writeSettings,
+ boolean keepArtProfile) throws SystemDeleteException {
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
executeDeletePackageLIF(action, packageName, deleteCodeAndResources, allUserHandles,
- writeSettings);
+ writeSettings, keepArtProfile);
}
}
@@ -445,11 +445,14 @@ final class DeletePackageHelper {
@GuardedBy("mPm.mInstallLock")
private void executeDeletePackageLIF(DeletePackageAction action,
String packageName, boolean deleteCodeAndResources,
- @NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
+ @NonNull int[] allUserHandles, boolean writeSettings, boolean keepArtProfile)
+ throws SystemDeleteException {
final PackageSetting ps = action.mDeletingPs;
final PackageRemovedInfo outInfo = action.mRemovedInfo;
final UserHandle user = action.mUser;
- final int flags = action.mFlags;
+ final int flags =
+ keepArtProfile ? action.mFlags | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES
+ : action.mFlags;
final boolean systemApp = PackageManagerServiceUtils.isSystemApp(ps);
// We need to get the permission state before package state is (potentially) destroyed.
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 764a22c3b94d..0b58c759b284 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -70,16 +70,17 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
+import com.android.server.pinner.PinnerService;
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.local.PackageManagerLocalImpl;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -790,16 +791,6 @@ public final class DexOptHelper {
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-
- // This mirrors logic from commitReconciledScanResultLocked, where the library
- // files needed for dexopt are assigned.
- PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
- // Unfortunately, the updated system app flag is only tracked on this
- // PackageSetting
- boolean isUpdatedSystemApp =
- installRequest.getScannedPackageSetting().isUpdatedSystemApp();
- realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
-
DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
installRequest, dexoptOptions);
installRequest.onDexoptFinished(dexOptResult);
@@ -820,10 +811,16 @@ public final class DexOptHelper {
final PackageSetting ps = installRequest.getScannedPackageSetting();
final String packageName = ps.getPackageName();
+ PackageSetting uncommittedPs = null;
+ if (Flags.improveInstallFreeze()) {
+ uncommittedPs = ps;
+ }
+
PackageManagerLocal packageManagerLocal =
LocalManagerRegistry.getManager(PackageManagerLocal.class);
try (PackageManagerLocal.FilteredSnapshot snapshot =
- packageManagerLocal.withFilteredSnapshot()) {
+ PackageManagerLocalImpl.withFilteredSnapshot(packageManagerLocal,
+ uncommittedPs)) {
boolean ignoreDexoptProfile =
(installRequest.getInstallFlags()
& PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index c5ec73b4e2b8..c4e981d487bc 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -123,7 +123,7 @@ public final class DistractingPackageHelper {
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
- mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(),
+ mBroadcastHelper.sendDistractingPackagesChanged(mPm::snapshotComputer,
changedPackages, changedUids.toArray(), userId, restrictionFlags);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -198,7 +198,7 @@ public final class DistractingPackageHelper {
if (!changedPackages.isEmpty()) {
final String[] packageArray = changedPackages.toArray(
new String[changedPackages.size()]);
- mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(),
+ mBroadcastHelper.sendDistractingPackagesChanged(mPm::snapshotComputer,
packageArray, changedUids.toArray(), userId, RESTRICTION_NONE);
mPm.scheduleWritePackageRestrictions(userId);
}
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
new file mode 100644
index 000000000000..c0ddebeb9868
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageInstaller.ACTION_INSTALL_DEPENDENCY;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+import static android.os.Process.SYSTEM_UID;
+
+import android.annotation.NonNull;
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerService;
+import android.content.pm.parsing.PackageLite;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to interact with SDK Dependency Installer service.
+ */
+public class InstallDependencyHelper {
+ private static final String TAG = InstallDependencyHelper.class.getSimpleName();
+ private static final boolean DEBUG = true;
+ private static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER =
+ "android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
+ // The maximum amount of time to wait before the system unbinds from the verifier.
+ private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
+ private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
+
+ private final Context mContext;
+ private final SharedLibrariesImpl mSharedLibraries;
+ private final PackageInstallerService mPackageInstallerService;
+ @GuardedBy("mTrackers")
+ private final List<DependencyInstallTracker> mTrackers = new ArrayList<>();
+ @GuardedBy("mRemoteServices")
+ private final ArrayMap<Integer, ServiceConnector<IDependencyInstallerService>> mRemoteServices =
+ new ArrayMap<>();
+
+ InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries,
+ PackageInstallerService packageInstallerService) {
+ mContext = context;
+ mSharedLibraries = sharedLibraries;
+ mPackageInstallerService = packageInstallerService;
+ }
+
+ void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
+ Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+ CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
+ try {
+ resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
+ } catch (PackageManagerException e) {
+ callback.onError(e);
+ } catch (Exception e) {
+ onError(callback, e.getMessage());
+ }
+ }
+
+
+ private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
+ int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
+ final List<SharedLibraryInfo> missing =
+ mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+
+ if (missing.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
+ }
+ // No need for dependency resolution. Move to installation directly.
+ callback.onResult(null);
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Missing dependencies found for pkg: " + pkg.getPackageName()
+ + " user: " + userId);
+ }
+
+ if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
+ onError(callback, "Dependency Installer Service not found");
+ return;
+ }
+
+ IDependencyInstallerCallback serviceCallback =
+ new DependencyInstallerCallbackCallOnce(handler, callback, userId);
+ boolean scheduleSuccess;
+ synchronized (mRemoteServices) {
+ scheduleSuccess = mRemoteServices.get(userId).run(service -> {
+ service.onDependenciesRequired(missing,
+ new DependencyInstallerCallback(serviceCallback.asBinder()));
+ });
+ }
+ if (!scheduleSuccess) {
+ onError(callback, "Failed to schedule job on Dependency Installer Service");
+ }
+ }
+
+ void notifySessionComplete(int sessionId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Session complete for " + sessionId);
+ }
+ synchronized (mTrackers) {
+ List<DependencyInstallTracker> completedTrackers = new ArrayList<>();
+ for (DependencyInstallTracker tracker: mTrackers) {
+ if (!tracker.onSessionComplete(sessionId)) {
+ completedTrackers.add(tracker);
+ }
+ }
+ mTrackers.removeAll(completedTrackers);
+ }
+ }
+
+ private static void onError(CallOnceProxy callback, String msg) {
+ PackageManagerException pe = new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg);
+ callback.onError(pe);
+ if (DEBUG) {
+ Slog.i(TAG, "Orig session error: " + msg);
+ }
+ }
+
+ private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
+ Computer snapshot) {
+ synchronized (mRemoteServices) {
+ if (mRemoteServices.containsKey(userId)) {
+ if (DEBUG) {
+ Slog.i(TAG, "DependencyInstallerService for user " + userId + " already bound");
+ }
+ return true;
+ }
+ }
+
+ Slog.i(TAG, "Attempting to bind to Dependency Installer Service for user " + userId);
+
+ RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ if (roleManager == null) {
+ Slog.w(TAG, "Cannot find RoleManager system service");
+ return false;
+ }
+ List<String> holders = roleManager.getRoleHoldersAsUser(
+ ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId));
+ if (holders.isEmpty()) {
+ Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " + userId);
+ return false;
+ }
+
+ Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
+ serviceIntent.setPackage(holders.getFirst());
+ List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
+ serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
+ userId, SYSTEM_UID, Process.INVALID_PID,
+ /*includeInstantApps*/ false, /*resolveForStart*/ false);
+
+ if (resolvedIntents.isEmpty()) {
+ Slog.w(TAG, "No package holding ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user "
+ + userId);
+ return false;
+ }
+
+ ResolveInfo resolveInfo = resolvedIntents.getFirst();
+ ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+ serviceIntent.setComponent(componentName);
+
+ ServiceConnector<IDependencyInstallerService> serviceConnector =
+ new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent,
+ Context.BIND_AUTO_CREATE, userId,
+ IDependencyInstallerService.Stub::asInterface) {
+ @Override
+ protected Handler getJobHandler() {
+ return handler;
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return REQUEST_TIMEOUT_MILLIS;
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return UNBIND_TIMEOUT_MILLIS;
+ }
+ };
+
+
+ synchronized (mRemoteServices) {
+ // Some other thread managed to connect to the service first
+ if (mRemoteServices.containsKey(userId)) {
+ return true;
+ }
+ mRemoteServices.put(userId, serviceConnector);
+ // Block the lock until we connect to the service
+ serviceConnector.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onDisconnected(@NonNull IDependencyInstallerService service) {
+ Slog.w(TAG,
+ "DependencyInstallerService " + componentName + " is disconnected");
+ destroy();
+ }
+
+ @Override
+ public void onBinderDied() {
+ Slog.w(TAG, "DependencyInstallerService " + componentName + " has died");
+ destroy();
+ }
+
+ private void destroy() {
+ synchronized (mRemoteServices) {
+ if (mRemoteServices.containsKey(userId)) {
+ mRemoteServices.get(userId).unbind();
+ mRemoteServices.remove(userId);
+ }
+ }
+ }
+
+ });
+ AndroidFuture<IDependencyInstallerService> unusedFuture = serviceConnector.connect();
+ }
+ Slog.i(TAG, "Successfully bound to Dependency Installer Service for user " + userId);
+ return true;
+ }
+
+ /**
+ * Ensure we call one of the outcomes only once, on the right handler.
+ *
+ * Repeated calls will be no-op.
+ */
+ private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> {
+ private final Handler mHandler;
+ private final OutcomeReceiver<Void, PackageManagerException> mCallback;
+ @GuardedBy("this")
+ private boolean mCalled = false;
+
+ CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) {
+ mHandler = handler;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(Void result) {
+ synchronized (this) {
+ if (!mCalled) {
+ mHandler.post(() -> {
+ mCallback.onResult(null);
+ });
+ mCalled = true;
+ }
+ }
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException error) {
+ synchronized (this) {
+ if (!mCalled) {
+ mHandler.post(() -> {
+ mCallback.onError(error);
+ });
+ mCalled = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Ensure we call one of the outcomes only once, on the right handler.
+ *
+ * Repeated calls will be no-op.
+ */
+ private class DependencyInstallerCallbackCallOnce extends IDependencyInstallerCallback.Stub {
+
+ private final Handler mHandler;
+ private final CallOnceProxy mCallback;
+ private final int mUserId;
+
+ @GuardedBy("this")
+ private boolean mDependencyInstallerCallbackInvoked = false;
+
+ DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback, int userId) {
+ mHandler = handler;
+ mCallback = callback;
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
+ synchronized (this) {
+ if (mDependencyInstallerCallbackInvoked) {
+ throw new IllegalStateException(
+ "Callback is being or has been already processed");
+ }
+ mDependencyInstallerCallbackInvoked = true;
+ }
+
+
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved started");
+ }
+
+ try {
+ // Before creating any tracker, validate the arguments
+ ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds);
+
+ if (validSessionIds.isEmpty()) {
+ mCallback.onResult(null);
+ return;
+ }
+
+ // Create a tracker now if there are any pending sessions remaining.
+ DependencyInstallTracker tracker = new DependencyInstallTracker(
+ mCallback, validSessionIds);
+ synchronized (mTrackers) {
+ mTrackers.add(tracker);
+ }
+
+ // By the time the tracker was created, some of the sessions in validSessionIds
+ // could have finished. Avoid waiting for them indefinitely.
+ for (int sessionId : validSessionIds) {
+ SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+
+ // Don't wait for sessions that finished already
+ if (sessionInfo == null) {
+ Binder.withCleanCallingIdentity(() -> {
+ notifySessionComplete(sessionId);
+ });
+ }
+ }
+ } catch (Exception e) {
+ // Allow calling the callback again
+ synchronized (this) {
+ mDependencyInstallerCallbackInvoked = false;
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public void onFailureToResolveAllDependencies() throws RemoteException {
+ synchronized (this) {
+ if (mDependencyInstallerCallbackInvoked) {
+ throw new IllegalStateException(
+ "Callback is being or has been already processed");
+ }
+ mDependencyInstallerCallbackInvoked = true;
+ }
+
+ Binder.withCleanCallingIdentity(() -> {
+ onError(mCallback, "Failed to resolve all dependencies automatically");
+ });
+ }
+
+ private ArraySet<Integer> validateSessionIds(int[] sessionIds) {
+ // Before creating any tracker, validate the arguments
+ ArraySet<Integer> validSessionIds = new ArraySet<>();
+
+ List<SessionInfo> historicalSessions = null;
+ for (int i = 0; i < sessionIds.length; i++) {
+ int sessionId = sessionIds[i];
+ SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+
+ // Continue waiting if session exists and hasn't passed or failed yet.
+ if (sessionInfo != null) {
+ if (sessionInfo.isSessionFailed) {
+ throw new IllegalArgumentException("Session already finished: "
+ + sessionId);
+ }
+
+ // Wait for session to finish install if it's not already successful.
+ if (!sessionInfo.isSessionApplied) {
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved pending session: " + sessionId);
+ }
+ validSessionIds.add(sessionId);
+ }
+
+ // An applied session found. No need to check historical session anymore.
+ continue;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved cleaning up finished"
+ + " session: " + sessionId);
+ }
+
+ if (historicalSessions == null) {
+ historicalSessions = mPackageInstallerService.getHistoricalSessions(
+ mUserId).getList();
+ }
+
+ sessionInfo = historicalSessions.stream().filter(
+ s -> s.sessionId == sessionId).findFirst().orElse(null);
+
+ if (sessionInfo == null) {
+ throw new IllegalArgumentException("Failed to find session: " + sessionId);
+ }
+
+ // Historical session must have been successful, otherwise throw IAE.
+ if (!sessionInfo.isSessionApplied) {
+ throw new IllegalArgumentException("Session already finished: " + sessionId);
+ }
+ }
+
+ return validSessionIds;
+ }
+ }
+
+ /**
+ * Tracks a list of session ids against a particular callback.
+ *
+ * If all the sessions completes successfully, it invokes the positive flow. If any of the
+ * sessions fails, it invokes the failure flow immediately.
+ */
+ // TODO(b/372862145): Determine and add support for rebooting while dependency is being resolved
+ private static class DependencyInstallTracker {
+ private final CallOnceProxy mCallback;
+ @GuardedBy("this")
+ private final ArraySet<Integer> mPendingSessionIds;
+
+ DependencyInstallTracker(CallOnceProxy callback, ArraySet<Integer> pendingSessionIds) {
+ mCallback = callback;
+ mPendingSessionIds = pendingSessionIds;
+ }
+
+ /**
+ * Process a session complete event.
+ *
+ * Returns true if we still need to continue tracking.
+ */
+ public boolean onSessionComplete(int sessionId) {
+ synchronized (this) {
+ if (!mPendingSessionIds.contains(sessionId)) {
+ // This had no impact on tracker, so continue tracking
+ return true;
+ }
+
+ mPendingSessionIds.remove(sessionId);
+ if (mPendingSessionIds.isEmpty()) {
+ mCallback.onResult(null);
+ return false; // Nothing to track anymore
+ }
+ return true; // Keep on tracking
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d4efb5c2951e..502384d81a4a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -85,6 +85,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
@@ -133,9 +134,11 @@ import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Message;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
+import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -148,6 +151,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.ExceptionUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -173,7 +177,6 @@ import com.android.internal.util.CollectionUtils;
import com.android.server.EventLogTags;
import com.android.server.SystemConfig;
import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -209,6 +212,10 @@ import java.util.concurrent.ExecutorService;
final class InstallPackageHelper {
+ // One minute over PM WATCHDOG_TIMEOUT
+ private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
+ private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages";
+
private final PackageManagerService mPm;
private final AppDataHelper mAppDataHelper;
private final BroadcastHelper mBroadcastHelper;
@@ -217,14 +224,16 @@ final class InstallPackageHelper {
private final IncrementalManager mIncrementalManager;
private final ApexManager mApexManager;
private final DexManager mDexManager;
- private final ArtManagerService mArtManagerService;
private final Context mContext;
- private final PackageDexOptimizer mPackageDexOptimizer;
private final PackageAbiHelper mPackageAbiHelper;
private final SharedLibrariesImpl mSharedLibraries;
private final PackageManagerServiceInjector mInjector;
private final UpdateOwnershipHelper mUpdateOwnershipHelper;
+ private final Object mInternalLock = new Object();
+ @GuardedBy("mInternalLock")
+ private PowerManager.WakeLock mInstallingWakeLock;
+
// TODO(b/198166813): remove PMS dependency
InstallPackageHelper(PackageManagerService pm,
AppDataHelper appDataHelper,
@@ -240,9 +249,7 @@ final class InstallPackageHelper {
mIncrementalManager = pm.mInjector.getIncrementalManager();
mApexManager = pm.mInjector.getApexManager();
mDexManager = pm.mInjector.getDexManager();
- mArtManagerService = pm.mInjector.getArtManagerService();
mContext = pm.mInjector.getContext();
- mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
mPackageAbiHelper = pm.mInjector.getAbiHelper();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
@@ -1012,41 +1019,185 @@ final class InstallPackageHelper {
boolean success = false;
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
+ final long acquireTime = acquireWakeLock(requests.size());
try {
CriticalEventLog.getInstance().logInstallPackagesStarted();
-
if (prepareInstallPackages(requests)
&& scanInstallPackages(requests, createdAppId, versionInfos)) {
List<ReconciledPackage> reconciledPackages =
reconcileInstallPackages(requests, versionInfos);
- if (reconciledPackages != null && commitInstallPackages(reconciledPackages)) {
- success = true;
+ if (reconciledPackages == null) {
+ return;
+ }
+ if (renameAndUpdatePaths(requests)) {
+ // rename before dexopt because art will encoded the path in the odex/vdex file
+ if (Flags.improveInstallFreeze()) {
+ prepPerformDexoptIfNeeded(reconciledPackages);
+ }
+ if (commitInstallPackages(reconciledPackages)) {
+ success = true;
+ }
}
}
} finally {
completeInstallProcess(requests, createdAppId, success);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ releaseWakeLock(acquireTime, requests.size());
}
}
- private boolean prepareInstallPackages(List<InstallRequest> requests) {
- // TODO: will remove the locking after doRename is moved out of prepare
+ private long acquireWakeLock(int count) {
+ if (!mPm.isSystemReady()) {
+ return -1;
+ }
+ synchronized (mInternalLock) {
+ if (mInstallingWakeLock == null) {
+ PowerManager pwm = mContext.getSystemService(PowerManager.class);
+ if (pwm != null) {
+ mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ INSTALLER_WAKE_LOCK_TAG);
+ } else {
+ Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock");
+ return -1;
+ }
+ }
+
+ mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count);
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ private void releaseWakeLock(final long acquireTime, int count) {
+ if (acquireTime < 0) {
+ return;
+ }
+ synchronized (mInternalLock) {
+ try {
+ if (mInstallingWakeLock == null) {
+ return;
+ }
+ if (mInstallingWakeLock.isHeld()) {
+ mInstallingWakeLock.release();
+ }
+ } catch (RuntimeException e) {
+ Slog.wtf(TAG, "Error while releasing installer lock", e);
+ }
+ }
+ }
+
+ private int[] getNewUsers(InstallRequest installRequest, int[] allUsers)
+ throws PackageManagerException {
+ final int userId = installRequest.getUserId();
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT
+ && !mPm.mUserManager.exists(userId)) {
+ throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
+ "User " + userId + " doesn't exist or has been removed");
+ }
+
+ final IntArray newUserIds = new IntArray();
+ if (userId != UserHandle.USER_ALL) {
+ newUserIds.add(userId);
+ } else if (allUsers != null) {
+ final int[] installedForUsers = installRequest.getOriginUsers();
+ for (int currentUserId : allUsers) {
+ final boolean installedForCurrentUser = ArrayUtils.contains(
+ installedForUsers, currentUserId);
+ final boolean restrictedByPolicy =
+ mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_INSTALL_APPS)
+ || mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_DEBUGGING_FEATURES);
+ if (installedForCurrentUser || !restrictedByPolicy) {
+ newUserIds.add(currentUserId);
+ }
+ }
+ }
+
+ if (newUserIds.size() == 0) {
+ throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
+ "User " + userId + " doesn't exist or has been removed");
+ } else {
+ return newUserIds.toArray();
+ }
+ }
+
+ private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
+ final InstallRequest request = reconciledPkg.mInstallRequest;
+ // prepare profiles
+ final PackageSetting ps = request.getScannedPackageSetting();
+ final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
+ final int[] allUsers = mPm.mUserManager.getUserIds();
+ if (reconciledPkg.mCollectedSharedLibraryInfos != null
+ || (oldPkgSetting != null
+ && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
+ // Reconcile if the new package or the old package uses shared libraries.
+ // It is possible that the old package uses shared libraries but the new
+ // one doesn't.
+ mSharedLibraries.executeSharedLibrariesUpdate(request.getParsedPackage(), ps,
+ null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
+ }
+ try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ final int[] newUsers = getNewUsers(request, allUsers);
+ // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
+ mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, newUsers);
+ if (request.isClearCodeCache()) {
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
+ } catch (PackageManagerException e) {
+ request.setError(e.error, e.getMessage());
+ return;
+ }
+ request.setKeepArtProfile(true);
+ DexOptHelper.performDexoptIfNeeded(request, mDexManager, mContext, null);
+ }
+ }
+
+ private boolean renameAndUpdatePaths(List<InstallRequest> requests) {
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
for (InstallRequest request : requests) {
+ ParsedPackage parsedPackage = request.getParsedPackage();
+ final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
+ if (isApex) {
+ continue;
+ }
try {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
- request.onPrepareStarted();
- preparePackageLI(request);
- } catch (PrepareFailure prepareFailure) {
- request.setError(prepareFailure.error,
- prepareFailure.getMessage());
- request.setOriginPackage(prepareFailure.mConflictingPackage);
- request.setOriginPermission(prepareFailure.mConflictingPermission);
+ doRenameLI(request, parsedPackage);
+ setUpFsVerity(parsedPackage);
+ } catch (Installer.InstallerException | IOException | DigestException
+ | NoSuchAlgorithmException | PrepareFailure e) {
+ request.setError(PackageManagerException.INTERNAL_ERROR_VERITY_SETUP,
+ "Failed to set up verity: " + e);
return false;
- } finally {
- request.onPrepareFinished();
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+
+ // update paths that are set before renaming
+ PackageSetting scannedPackageSetting = request.getScannedPackageSetting();
+ scannedPackageSetting.setPath(new File(parsedPackage.getPath()));
+ scannedPackageSetting.setLegacyNativeLibraryPath(
+ parsedPackage.getNativeLibraryRootDir());
+ }
+ return true;
+ }
+ }
+
+ private boolean prepareInstallPackages(List<InstallRequest> requests) {
+ for (InstallRequest request : requests) {
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
+ request.onPrepareStarted();
+ preparePackage(request);
+ } catch (PrepareFailure prepareFailure) {
+ request.setError(prepareFailure.error,
+ prepareFailure.getMessage());
+ request.setOriginPackage(prepareFailure.mConflictingPackage);
+ request.setOriginPermission(prepareFailure.mConflictingPermission);
+ return false;
+ } finally {
+ request.onPrepareFinished();
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
return true;
@@ -1054,71 +1205,68 @@ final class InstallPackageHelper {
private boolean scanInstallPackages(List<InstallRequest> requests,
Map<String, Boolean> createdAppId, Map<String, Settings.VersionInfo> versionInfos) {
- // TODO(b/362840929): remove locker
- try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
- final Set<String> scannedPackages = new ArraySet<>(requests.size());
- for (InstallRequest request : requests) {
- final ParsedPackage packageToScan = request.getParsedPackage();
- if (packageToScan == null) {
- request.setError(INSTALL_FAILED_SESSION_INVALID,
- "Failed to obtain package to scan");
+ final Set<String> scannedPackages = new ArraySet<>(requests.size());
+ for (InstallRequest request : requests) {
+ final ParsedPackage packageToScan = request.getParsedPackage();
+ if (packageToScan == null) {
+ request.setError(INSTALL_FAILED_SESSION_INVALID,
+ "Failed to obtain package to scan");
+ return false;
+ }
+ request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+ final String packageName = packageToScan.getPackageName();
+ try {
+ request.onScanStarted();
+ final ScanResult scanResult = scanPackageTraced(request.getParsedPackage(),
+ request.getParseFlags(), request.getScanFlags(),
+ System.currentTimeMillis(), request.getUser(),
+ request.getAbiOverride());
+ request.setScanResult(scanResult);
+ request.onScanFinished();
+ if (!scannedPackages.add(packageName)) {
+ request.setError(
+ PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
+ "Duplicate package "
+ + packageName
+ + " in multi-package install request.");
return false;
}
- request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
- final String packageName = packageToScan.getPackageName();
- try {
- request.onScanStarted();
- final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
- request.getParseFlags(), request.getScanFlags(),
- System.currentTimeMillis(), request.getUser(),
- request.getAbiOverride());
- request.setScanResult(scanResult);
- request.onScanFinished();
- if (!scannedPackages.add(packageName)) {
- request.setError(
- PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
- "Duplicate package "
- + packageName
- + " in multi-package install request.");
- return false;
- }
- if (!checkNoAppStorageIsConsistent(
- request.getScanRequestOldPackage(), packageToScan)) {
- // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
- // signatures. Is there a better error code?
- request.setError(
- INSTALL_FAILED_UPDATE_INCOMPATIBLE,
- "Update attempted to change value of "
- + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
- return false;
- }
- final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
- final boolean isSdkLibrary = packageToScan.isSdkLibrary();
- if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
- request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
- } else {
- createdAppId.put(packageName, optimisticallyRegisterAppId(request));
- }
- versionInfos.put(packageName,
- mPm.getSettingsVersionForPackage(packageToScan));
- } catch (PackageManagerException e) {
- request.setError("Scanning Failed.", e);
+ if (!checkNoAppStorageIsConsistent(
+ request.getScanRequestOldPackage(), packageToScan)) {
+ // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
+ // signatures. Is there a better error code?
+ request.setError(
+ INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "Update attempted to change value of "
+ + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
return false;
}
- if (request.isArchived()) {
- final SparseArray<String> responsibleInstallerTitles =
- PackageArchiver.getResponsibleInstallerTitles(mContext,
- mPm.snapshotComputer(), request.getInstallSource(),
- request.getUserId(), mPm.mUserManager.getUserIds());
- if (responsibleInstallerTitles == null
- || responsibleInstallerTitles.size() == 0) {
- request.setError(PackageManagerException.ofInternalError(
- "Failed to obtain the responsible installer info",
- INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
- return false;
- }
- request.setResponsibleInstallerTitles(responsibleInstallerTitles);
+ final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
+ final boolean isSdkLibrary = packageToScan.isSdkLibrary();
+ if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
+ request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
+ } else {
+ createdAppId.put(packageName, optimisticallyRegisterAppId(request));
+ }
+ versionInfos.put(packageName,
+ mPm.getSettingsVersionForPackage(packageToScan));
+ } catch (PackageManagerException e) {
+ request.setError("Scanning Failed.", e);
+ return false;
+ }
+ if (request.isArchived()) {
+ final SparseArray<String> responsibleInstallerTitles =
+ PackageArchiver.getResponsibleInstallerTitles(mContext,
+ mPm.snapshotComputer(), request.getInstallSource(),
+ request.getUserId(), mPm.mUserManager.getUserIds());
+ if (responsibleInstallerTitles == null
+ || responsibleInstallerTitles.size() == 0) {
+ request.setError(PackageManagerException.ofInternalError(
+ "Failed to obtain the responsible installer info",
+ INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
+ return false;
}
+ request.setResponsibleInstallerTitles(responsibleInstallerTitles);
}
}
return true;
@@ -1215,7 +1363,6 @@ final class InstallPackageHelper {
}
}
- @GuardedBy("mPm.mInstallLock")
private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg, AndroidPackage newPkg) {
if (oldPkg == null) {
// New install, nothing to check against.
@@ -1231,8 +1378,7 @@ final class InstallPackageHelper {
return newProp != null && newProp.getBoolean();
}
- @GuardedBy("mPm.mInstallLock")
- private void preparePackageLI(InstallRequest request) throws PrepareFailure {
+ private void preparePackage(InstallRequest request) throws PrepareFailure {
final int[] allUsers = mPm.mUserManager.getUserIds();
final int installFlags = request.getInstallFlags();
final boolean onExternal = request.getVolumeUuid() != null;
@@ -1740,18 +1886,7 @@ final class InstallPackageHelper {
}
}
- if (!isApex) {
- doRenameLI(request, parsedPackage);
-
- try {
- setUpFsVerity(parsedPackage);
- } catch (Installer.InstallerException | IOException | DigestException
- | NoSuchAlgorithmException e) {
- throw PrepareFailure.ofInternalError(
- "Failed to set up verity: " + e,
- PackageManagerException.INTERNAL_ERROR_VERITY_SETUP);
- }
- } else {
+ if (isApex) {
// Use the path returned by apexd
parsedPackage.setPath(request.getApexInfo().modulePath);
parsedPackage.setBaseApkPath(request.getApexInfo().modulePath);
@@ -2093,7 +2228,21 @@ final class InstallPackageHelper {
// Reflect the rename in scanned details
try {
- parsedPackage.setPath(afterCodeFile.getCanonicalPath());
+ String afterCanonicalPath = afterCodeFile.getCanonicalPath();
+ String beforeCanonicalPath = beforeCodeFile.getCanonicalPath();
+ parsedPackage.setPath(afterCanonicalPath);
+
+ parsedPackage.setNativeLibraryDir(
+ parsedPackage.getNativeLibraryDir()
+ .replace(beforeCanonicalPath, afterCanonicalPath));
+ parsedPackage.setNativeLibraryRootDir(
+ parsedPackage.getNativeLibraryRootDir()
+ .replace(beforeCanonicalPath, afterCanonicalPath));
+ String secondaryNativeLibraryDir = parsedPackage.getSecondaryNativeLibraryDir();
+ if (secondaryNativeLibraryDir != null) {
+ parsedPackage.setSecondaryNativeLibraryDir(
+ secondaryNativeLibraryDir.replace(beforeCanonicalPath, afterCanonicalPath));
+ }
} catch (IOException e) {
Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
throw new PrepareFailure(PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE,
@@ -2103,6 +2252,7 @@ final class InstallPackageHelper {
afterCodeFile, parsedPackage.getBaseApkPath()));
parsedPackage.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, parsedPackage.getSplitCodePaths()));
+ request.updateAllCodePaths(AndroidPackageUtils.getAllCodePaths(parsedPackage));
}
// TODO(b/168126411): Once staged install flow starts using the same folder as non-staged
@@ -2303,7 +2453,7 @@ final class InstallPackageHelper {
// Settings will be written during the call to updateSettingsLI().
mDeletePackageHelper.executeDeletePackage(
reconciledPkg.mDeletePackageAction, packageName,
- true, allUsers, false);
+ true, allUsers, false, installRequest.isKeepArtProfile());
} catch (SystemDeleteException e) {
if (mPm.mIsEngBuild) {
throw new RuntimeException("Unexpected failure", e);
@@ -2626,20 +2776,22 @@ final class InstallPackageHelper {
incrementalStorages.add(storage);
}
- // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
- mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
- if (installRequest.isClearCodeCache()) {
- mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
- FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
- | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- }
if (installRequest.isInstallReplace() && pkg != null) {
mDexManager.notifyPackageUpdated(packageName,
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
+ if (!Flags.improveInstallFreeze()) {
+ // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
+ mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
+ if (installRequest.isClearCodeCache()) {
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
- DexOptHelper.performDexoptIfNeeded(installRequest, mDexManager, mContext,
- mPm.mInstallLock.getRawLock());
+ DexOptHelper.performDexoptIfNeeded(installRequest, mDexManager, mContext,
+ mPm.mInstallLock.getRawLock());
+ }
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
incrementalStorages);
@@ -2832,7 +2984,7 @@ final class InstallPackageHelper {
}
}
- public void sendPendingBroadcasts() {
+ public void sendPendingBroadcasts(String reasonForTrace) {
String[] packages;
ArrayList<String>[] components;
int numBroadcasts = 0, numUsers;
@@ -2876,7 +3028,8 @@ final class InstallPackageHelper {
// Send broadcasts
for (int i = 0; i < numBroadcasts; i++) {
mBroadcastHelper.sendPackageChangedBroadcast(snapshot, packages[i],
- true /* dontKillApp */, components[i], uids[i], null /* reason */);
+ true /* dontKillApp */, components[i], uids[i], null /* reason */,
+ reasonForTrace);
}
}
@@ -2933,7 +3086,7 @@ final class InstallPackageHelper {
mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
}
- mBroadcastHelper.sendPostInstallBroadcasts(mPm.snapshotComputer(), request, packageName,
+ mBroadcastHelper.sendPostInstallBroadcasts(mPm::snapshotComputer, request, packageName,
mPm.mRequiredPermissionControllerPackage, mPm.mRequiredVerifierPackages,
mPm.mRequiredInstallerPackage,
/* packageSender= */ mPm, launchedForRestore, killApp, update, archived);
@@ -3964,14 +4117,13 @@ final class InstallPackageHelper {
}
}
- @GuardedBy("mPm.mInstallLock")
- private ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
+ private ScanResult scanPackageTraced(ParsedPackage parsedPackage,
final @ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags, long currentTime,
@Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
try {
- return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
+ return scanPackageNew(parsedPackage, parseFlags, scanFlags, currentTime, user,
cpuAbiOverride);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -4048,8 +4200,7 @@ final class InstallPackageHelper {
realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride);
}
- @GuardedBy("mPm.mInstallLock")
- private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
+ private ScanResult scanPackageNew(@NonNull ParsedPackage parsedPackage,
final @ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags, long currentTime,
@Nullable UserHandle user, String cpuAbiOverride)
@@ -4080,7 +4231,7 @@ final class InstallPackageHelper {
initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName,
parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user,
cpuAbiOverride);
- return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest,
+ return ScanPackageUtils.scanPackageOnly(request, mPm.mInjector, mPm.mFactoryTest,
currentTime);
}
}
@@ -4134,7 +4285,7 @@ final class InstallPackageHelper {
ScanPackageUtils.applyPolicy(parsedPackage, scanFlags,
mPm.getPlatformPackage(), true);
final ScanResult scanResult =
- ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector,
+ ScanPackageUtils.scanPackageOnly(request, mPm.mInjector,
mPm.mFactoryTest, -1L);
if (scanResult.mExistingSettingCopied
&& scanResult.mRequest.mPkgSetting != null) {
@@ -4326,7 +4477,7 @@ final class InstallPackageHelper {
final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
? System.currentTimeMillis() : 0;
- final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
+ final ScanResult scanResult = scanPackageNew(parsedPackage, parseFlags,
scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
}
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ae7749b7dfe1..b0fe3a97af6e 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.Flags.improveInstallFreeze;
import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
@@ -170,6 +169,8 @@ final class InstallRequest {
private final boolean mHasAppMetadataFileFromInstaller;
+ private boolean mKeepArtProfile = false;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -615,6 +616,20 @@ final class InstallRequest {
return mScanResult.mDynamicSharedLibraryInfos;
}
+ public void updateAllCodePaths(List<String> paths) {
+ if (mScanResult.mSdkSharedLibraryInfo != null) {
+ mScanResult.mSdkSharedLibraryInfo.setAllCodePaths(paths);
+ }
+ if (mScanResult.mStaticSharedLibraryInfo != null) {
+ mScanResult.mStaticSharedLibraryInfo.setAllCodePaths(paths);
+ }
+ if (mScanResult.mDynamicSharedLibraryInfos != null) {
+ for (SharedLibraryInfo info : mScanResult.mDynamicSharedLibraryInfos) {
+ info.setAllCodePaths(paths);
+ }
+ }
+ }
+
@Nullable
public PackageSetting getScannedPackageSetting() {
assertScanResultExists();
@@ -1036,14 +1051,22 @@ final class InstallRequest {
}
public void onFreezeStarted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
public void onFreezeCompleted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
+
+ void setKeepArtProfile(boolean keepArtProfile) {
+ mKeepArtProfile = keepArtProfile;
+ }
+
+ boolean isKeepArtProfile() {
+ return mKeepArtProfile;
+ }
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index efd58ed6edcc..286333cb83a9 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -88,6 +88,7 @@ import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
+import android.database.ContentObserver;
import android.graphics.Rect;
import android.multiuser.Flags;
import android.net.Uri;
@@ -249,6 +250,7 @@ public class LauncherAppsService extends SystemService {
private PackageInstallerService mPackageInstallerService;
final LauncherAppsServiceInternal mInternal;
+ private SecureSettingsObserver mSecureSettingsObserver;
@NonNull
private final RemoteCallbackList<IDumpCallback> mDumpCallbacks =
@@ -278,6 +280,7 @@ public class LauncherAppsService extends SystemService {
mCallbackHandler = BackgroundThread.getHandler();
mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
mInternal = new LocalService();
+ registerSettingsObserver();
}
@VisibleForTesting
@@ -1674,18 +1677,32 @@ public class LauncherAppsService extends SystemService {
@Override
public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component,
UserHandle user) {
+ try {
+ Log.d(TAG,
+ "getActivityLaunchIntent callingPackage=" + callingPackage + " component="
+ + component + " user=" + user);
+ } catch (Exception e) {
+ Log.e(TAG, "getActivityLaunchIntent is called and error occurred when"
+ + " printing the logs", e);
+ }
if (mContext.checkPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS,
injectBinderCallingPid(), injectBinderCallingUid())
!= PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "getActivityLaunchIntent no permission callingPid="
+ + injectBinderCallingPid() + " callingUid=" + injectBinderCallingUid());
throw new SecurityException("Permission START_TASKS_FROM_RECENTS required");
}
if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
+ Log.d(TAG, "getActivityLaunchIntent cannot access profile user="
+ + user.getIdentifier());
throw new ActivityNotFoundException("Activity could not be found");
}
final Intent launchIntent = getMainActivityLaunchIntent(component, user,
false /* includeArchivedApps */);
if (launchIntent == null) {
+ Log.d(TAG, "getActivityLaunchIntent cannot access profile user="
+ + user.getIdentifier() + " component=" + component);
throw new SecurityException("Attempt to launch activity without "
+ " category Intent.CATEGORY_LAUNCHER " + component);
}
@@ -1965,6 +1982,17 @@ public class LauncherAppsService extends SystemService {
canLaunch = true;
}
if (!canLaunch) {
+ try {
+ Log.w(TAG, "getMainActivityLaunchIntent return null because it can't launch"
+ + " component=" + component + " user=" + user + " appsSize=" + size
+ + " includeArchivedApps=" + includeArchivedApps
+ + " isArchivingEnabled=" + isArchivingEnabled()
+ + " matchingArchivedAppActivityInfo="
+ + getMatchingArchivedAppActivityInfo(component, user));
+ } catch (Exception e) {
+ Log.e(TAG, "getMainActivityLaunchIntent return null and error occurred when"
+ + " printing the logs", e);
+ }
return null;
}
} finally {
@@ -2287,6 +2315,13 @@ public class LauncherAppsService extends SystemService {
}
}
+ void registerSettingsObserver() {
+ if (Flags.addLauncherUserConfig()) {
+ mSecureSettingsObserver = new SecureSettingsObserver();
+ mSecureSettingsObserver.register();
+ }
+ }
+
public static class ShortcutChangeHandler implements LauncherApps.ShortcutChangeCallback {
private final UserManagerInternal mUserManagerInternal;
@@ -2631,6 +2666,7 @@ public class LauncherAppsService extends SystemService {
}
final String[] packagesNullExtras = packagesWithoutExtras.toArray(
new String[packagesWithoutExtras.size()]);
+
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
@@ -2812,5 +2848,81 @@ public class LauncherAppsService extends SystemService {
shortcutId, sourceBounds, startActivityOptions, targetUserId);
}
}
+
+ class SecureSettingsObserver extends ContentObserver {
+
+ SecureSettingsObserver() {
+ super(mCallbackHandler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ if (uri.equals(
+ Settings.Secure.getUriFor(Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT))) {
+
+ // This setting key only apply to private profile at the moment
+ UserHandle privateProfile = getPrivateProfile();
+ if (privateProfile.getIdentifier() == UserHandle.USER_NULL) {
+ return;
+ }
+ final int n = mListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < n; i++) {
+ final IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ final BroadcastCookie cookie =
+ (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(cookie, privateProfile,
+ "onSecureSettingsChange")) {
+ Log.d(TAG, "onSecureSettingsChange: Skipping - profile not enabled"
+ + " or not accessible for package=" + cookie.packageName
+ + ", packageUid=" + cookie.callingUid);
+ continue;
+ }
+ try {
+ Log.d(TAG, "onUserConfigChanged: triggering onUserConfigChanged");
+ listener.onUserConfigChanged(
+ mUserManagerInternal.getLauncherUserInfo(
+ privateProfile.getIdentifier()));
+ } catch (RemoteException re) {
+ Slog.d(TAG, "onUserConfigChanged: Callback failed ", re);
+ }
+ }
+
+ } finally {
+ mListeners.finishBroadcast();
+ }
+ }
+ }
+
+ public void register() {
+ UserHandle privateProfile = getPrivateProfile();
+ int parentUserId;
+ if (privateProfile.getIdentifier() == UserHandle.USER_NULL) {
+ // No private space available, register the observer for the current user
+ parentUserId = mContext.getUserId();
+ } else {
+ parentUserId = mUserManagerInternal.getProfileParentId(
+ privateProfile.getIdentifier());
+ }
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT),
+ true, this, parentUserId);
+ }
+
+ public void unregister() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ private UserHandle getPrivateProfile() {
+ UserInfo[] userInfos = mUserManagerInternal.getUserInfos();
+ for (UserInfo u : userInfos) {
+ if (u.isPrivateProfile()) {
+ return UserHandle.of(u.id);
+ }
+ }
+ return UserHandle.of(UserHandle.USER_NULL);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java
index c66a9e98c1d3..09302996d228 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelper.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.util.ArraySet;
import android.util.Pair;
@@ -28,8 +29,6 @@ import com.android.server.pm.pkg.PackageStateInternal;
import java.io.File;
-
-
// TODO: Move to .parsing sub-package
@VisibleForTesting
public interface PackageAbiHelper {
@@ -79,6 +78,23 @@ public interface PackageAbiHelper {
AndroidPackage scannedPackage);
/**
+ * Checks alignment of APK and native libraries for 16KB device
+ *
+ * @param pkg AndroidPackage for which alignment check is being done
+ * @param libraryRoot directory for libraries
+ * @param nativeLibraryRootRequiresIsa use isa
+ * @param cpuAbiOverride ABI override mentioned in package
+ * @return {ApplicationInfo.PageSizeAppCompat} if successful or error code
+ * which suggests undefined mode
+ */
+ @ApplicationInfo.PageSizeAppCompatFlags
+ int checkPackageAlignment(
+ AndroidPackage pkg,
+ String libraryRoot,
+ boolean nativeLibraryRootRequiresIsa,
+ String cpuAbiOverride);
+
+ /**
* The native library paths and related properties that should be set on a
* {@link ParsedPackage}.
*/
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 9db4d33106ce..7229f070acf0 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -29,6 +29,7 @@ import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -625,4 +626,22 @@ final class PackageAbiHelperImpl implements PackageAbiHelper {
}
return adjustedAbi;
}
+
+ @Override
+ public int checkPackageAlignment(
+ AndroidPackage pkg,
+ String libraryRoot,
+ boolean nativeLibraryRootRequiresIsa,
+ String abiOverride) {
+ NativeLibraryHelper.Handle handle = null;
+ try {
+ handle = AndroidPackageUtils.createNativeLibraryHandle(pkg);
+ return NativeLibraryHelper.checkAlignmentForCompatMode(
+ handle, libraryRoot, nativeLibraryRootRequiresIsa, abiOverride);
+ } catch (IOException e) {
+ Slog.e(PackageManagerService.TAG, "Failed to check alignment of package : "
+ + pkg.getPackageName());
+ return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 76ea0b963036..4690e020b12a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -880,7 +880,8 @@ public class PackageArchiver {
PackageInstaller.STATUS_PENDING_USER_ACTION);
broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
broadcastIntent.putExtra(Intent.EXTRA_USER, user);
- sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
+ mPm.mHandler.post(
+ () -> sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent));
}
private void verifyUninstallPermissions() {
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 4ea405441030..0a067048be42 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -76,7 +76,7 @@ final class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
case SEND_PENDING_BROADCAST: {
- mPm.sendPendingBroadcasts();
+ mPm.sendPendingBroadcasts((String) msg.obj);
break;
}
case POST_INSTALL: {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1316df16027f..e1fcc6650650 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -50,6 +50,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -201,6 +202,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
Manifest.permission.USE_FULL_SCREEN_INTENT
);
+ private static final String ROLE_SYSTEM_APP_PROTECTION_SERVICE =
+ "android.app.role.SYSTEM_APP_PROTECTION_SERVICE";
+
final PackageArchiver mPackageArchiver;
private final Context mContext;
@@ -209,6 +213,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
private final StagingManager mStagingManager;
private AppOpsManager mAppOps;
+ private final InstallDependencyHelper mInstallDependencyHelper;
private final HandlerThread mInstallThread;
private final Handler mInstallHandler;
@@ -321,6 +326,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mGentleUpdateHelper = new GentleUpdateHelper(
context, mInstallThread.getLooper(), new AppStateHelper(context));
mPackageArchiver = new PackageArchiver(mContext, mPm);
+ mInstallDependencyHelper = new InstallDependencyHelper(mContext,
+ mPm.mInjector.getSharedLibrariesImpl(), this);
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -330,6 +337,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
return mStagingManager;
}
+ InstallDependencyHelper getInstallDependencyHelper() {
+ return mInstallDependencyHelper;
+ }
+
boolean okToSendBroadcasts() {
return mOkToSendBroadcasts;
}
@@ -517,7 +528,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
try {
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mStagingManager,
- mSessionsDir, this, mSilentUpdatePolicy);
+ mSessionsDir, this, mSilentUpdatePolicy,
+ mInstallDependencyHelper);
} catch (Exception e) {
Slog.e(TAG, "Could not read session", e);
continue;
@@ -1033,7 +1045,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
- false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
+ false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
+ mInstallDependencyHelper);
synchronized (mSessions) {
mSessions.put(sessionId, session);
@@ -1454,6 +1467,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
.createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE)
.setAdmin(callerPackageName)
.write();
+ } else if (isSystemAppProtectionRoleHolder(snapshot, userId, callingUid)) {
+ // Allow the SYSTEM_APP_PROTECTION_SERVICE role holder to silently uninstall, with a
+ // clean calling identity to get DELETE_PACKAGES permission
+ Binder.withCleanCallingIdentity(() ->
+ mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags)
+ );
} else {
ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
@@ -1475,6 +1494,29 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ private Boolean isSystemAppProtectionRoleHolder(
+ @NonNull Computer snapshot, int userId, int callingUid) {
+ if (!Flags.deletePackagesSilentlyBackport()) {
+ return false;
+ }
+ String holderPackageName = Binder.withCleanCallingIdentity(() -> {
+ RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
+ if (roleManager == null) {
+ return null;
+ }
+ List<String> holders = roleManager.getRoleHoldersAsUser(
+ ROLE_SYSTEM_APP_PROTECTION_SERVICE, UserHandle.of(userId));
+ if (holders.isEmpty()) {
+ return null;
+ }
+ return holders.get(0);
+ });
+ if (holderPackageName == null) {
+ return false;
+ }
+ return snapshot.getPackageUid(holderPackageName, /* flags= */ 0, userId) == callingUid;
+ }
+
@Override
public void uninstallExistingPackage(VersionedPackage versionedPackage,
String callerPackageName, IntentSender statusReceiver, int userId) {
@@ -2287,6 +2329,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ if (Flags.sdkDependencyInstaller()) {
+ mInstallDependencyHelper.notifySessionComplete(session.sessionId);
+ }
+
final File appIconFile = buildAppIconFile(session.sessionId);
if (appIconFile.exists()) {
appIconFile.delete();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9edbaa8fadd9..c799b8b969c8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -88,6 +88,7 @@ import android.content.pm.DataLoaderManager;
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.FileSystemControlParcel;
+import android.content.pm.Flags;
import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.IOnChecksumsReadyListener;
@@ -132,6 +133,7 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.Process;
@@ -407,6 +409,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
private final StagingManager mStagingManager;
+ private final InstallDependencyHelper mInstallDependencyHelper;
+
final int sessionId;
final int userId;
final SessionParams params;
@@ -1073,6 +1077,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean isInstallDpcPackagesPermissionGranted = (snapshot.checkUidPermission(
android.Manifest.permission.INSTALL_DPC_PACKAGES, mInstallerUid)
== PackageManager.PERMISSION_GRANTED);
+ boolean isInstallDependencyPackagesPermissionGranted = false;
+ if (Flags.sdkDependencyInstaller()) {
+ isInstallDependencyPackagesPermissionGranted = (snapshot.checkUidPermission(
+ android.Manifest.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES, mInstallerUid)
+ == PackageManager.PERMISSION_GRANTED);
+ }
// Also query the package uid for archived packages, so that the user confirmation
// dialog can be displayed for updating archived apps.
final int targetPackageUid = snapshot.getPackageUid(packageName,
@@ -1094,10 +1104,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
final boolean isEmergencyInstall =
isEmergencyInstallerEnabled(packageName, snapshot, userId, mInstallerUid);
+ boolean isSdkOrStaticLibraryInstall = false;
+ synchronized (mLock) {
+ if (mPackageLite != null) {
+ isSdkOrStaticLibraryInstall =
+ mPackageLite.isIsSdkLibrary() || mPackageLite.isIsStaticLibrary();
+ }
+ }
final boolean isPermissionGranted = isInstallPermissionGranted
|| (isUpdatePermissionGranted && isUpdate)
|| (isSelfUpdatePermissionGranted && isSelfUpdate)
- || (isInstallDpcPackagesPermissionGranted && hasDeviceAdminReceiver);
+ || (isInstallDpcPackagesPermissionGranted && hasDeviceAdminReceiver)
+ || (isInstallDependencyPackagesPermissionGranted && isSdkOrStaticLibraryInstall);
final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);
@@ -1169,7 +1187,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
boolean prepared, boolean committed, boolean destroyed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int sessionErrorCode,
- String sessionErrorMessage, DomainSet preVerifiedDomains) {
+ String sessionErrorMessage, DomainSet preVerifiedDomains,
+ InstallDependencyHelper installDependencyHelper) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1178,6 +1197,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mSilentUpdatePolicy = silentUpdatePolicy;
mHandler = new Handler(looper, mHandlerCallback);
mStagingManager = stagingManager;
+ mInstallDependencyHelper = installDependencyHelper;
this.sessionId = sessionId;
this.userId = userId;
@@ -1393,6 +1413,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.packageSource = params.packageSource;
info.applicationEnabledSettingPersistent = params.applicationEnabledSettingPersistent;
info.pendingUserActionReason = userActionRequirementToReason(mUserActionRequirement);
+ info.isAutoInstallingDependenciesEnabled = params.isAutoInstallDependenciesEnabled;
}
return info;
}
@@ -2593,6 +2614,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
maybeFinishChildSessions(error, msg);
}
+ private void onSessionDependencyResolveFailure(int error, String msg) {
+ Slog.e(TAG, "Failed to resolve dependency for session " + sessionId);
+ // Dispatch message to remove session from PackageInstallerService.
+ dispatchSessionFinished(error, msg, null);
+ maybeFinishChildSessions(error, msg);
+ }
+
private void onSystemDataLoaderUnrecoverable() {
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
@@ -2847,6 +2875,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// since installation is in progress.
activate();
}
+ try {
+ List<PackageInstallerSession> children = getChildSessions();
+ if (isMultiPackage()) {
+ for (PackageInstallerSession child : children) {
+ child.prepareInheritedFiles();
+ child.parseApk();
+ }
+ } else {
+ prepareInheritedFiles();
+ parseApk();
+ }
+ } catch (PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ final String errorMsg = PackageManager.installStatusToString(e.error, completeMsg);
+ setSessionFailed(e.error, errorMsg);
+ onSessionVerificationFailure(e.error, errorMsg);
+ }
if (mVerificationInProgress) {
Slog.w(TAG, "Verification is already in progress for session " + sessionId);
@@ -2866,12 +2911,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
List<PackageInstallerSession> children = getChildSessions();
if (isMultiPackage()) {
for (PackageInstallerSession child : children) {
- child.prepareInheritedFiles();
- child.parseApkAndExtractNativeLibraries();
+ child.extractNativeLibraries();
}
} else {
- prepareInheritedFiles();
- parseApkAndExtractNativeLibraries();
+ extractNativeLibraries();
}
verifyNonStaged();
} catch (PackageManagerException e) {
@@ -2988,7 +3031,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStageDirInUse = true;
}
- private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
+ private void parseApk() throws PackageManagerException {
synchronized (mLock) {
if (mStageDirInUse) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -3021,12 +3064,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// stage dir here.
// Besides, PackageLite may be null for staged sessions that don't complete
// pre-reboot verification.
- result = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
+ mPackageLite = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
} else {
- result = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
+ mPackageLite = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
}
- if (result != null) {
- mPackageLite = result;
+ }
+ }
+
+ private void extractNativeLibraries() throws PackageManagerException {
+ synchronized (mLock) {
+ if (mPackageLite != null) {
if (!isApexSession()) {
synchronized (mProgressLock) {
mInternalProgress = 0.5f;
@@ -3181,7 +3228,36 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/* extras= */ null, /* forPreapproval= */ false);
return;
}
- install();
+
+ if (Flags.sdkDependencyInstaller()
+ && params.isAutoInstallDependenciesEnabled
+ && !isMultiPackage()) {
+ resolveLibraryDependenciesIfNeeded();
+ } else {
+ install();
+ }
+ }
+
+
+ private void resolveLibraryDependenciesIfNeeded() {
+ synchronized (mLock) {
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+ mPm.snapshotComputer(), userId, mHandler,
+ new OutcomeReceiver<>() {
+
+ @Override
+ public void onResult(Void result) {
+ install();
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ setSessionFailed(e.error, completeMsg);
+ onSessionDependencyResolveFailure(e.error, completeMsg);
+ }
+ });
+ }
}
/**
@@ -4265,9 +4341,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
* @return the uid of the owner this session
*/
public int getInstallerUid() {
- synchronized (mLock) {
- return mInstallerUid;
- }
+ return mInstallerUid;
}
/**
@@ -5832,7 +5906,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@NonNull PackageManagerService pm, Looper installerThread,
@NonNull StagingManager stagingManager, @NonNull File sessionsDir,
@NonNull PackageSessionProvider sessionProvider,
- @NonNull SilentUpdatePolicy silentUpdatePolicy)
+ @NonNull SilentUpdatePolicy silentUpdatePolicy,
+ @NonNull InstallDependencyHelper installDependencyHelper)
throws IOException, XmlPullParserException {
final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -6036,6 +6111,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
installerUid, installSource, params, createdMillis, committedMillis, stageDir,
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
+ sessionErrorCode, sessionErrorMessage, preVerifiedDomains,
+ installDependencyHelper);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6f9d53d1320a..2154d37698a2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -204,7 +204,6 @@ import com.android.server.FgThread;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
-import com.android.server.PackageWatchdog;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.ThreadPriorityBooster;
@@ -214,6 +213,7 @@ import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.model.DeleteResult;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
+import com.android.server.crashrecovery.CrashRecoveryAdaptor;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.ArtManagerService;
@@ -3058,7 +3058,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mDexManager.writePackageDexUsageNow();
mDynamicCodeLogger.writeNow();
if (!refactorCrashrecovery()) {
- PackageWatchdog.getInstance(mContext).writeNow();
+ CrashRecoveryAdaptor.packageWatchdogWriteNow(mContext);
}
synchronized (mLock) {
@@ -3541,7 +3541,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPendingBroadcasts.addComponents(userId, packageName, updatedComponents);
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
- mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(SEND_PENDING_BROADCAST,
+ "reset_component_state_changed" /* obj */),
+ BROADCAST_DELAY);
}
}
@@ -3838,7 +3841,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPendingBroadcasts.addComponent(userId, componentPkgName, componentName.getClassName());
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
- mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(SEND_PENDING_BROADCAST,
+ "component_label_icon_changed" /* obj */), BROADCAST_DELAY);
}
}
@@ -4073,6 +4077,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPendingBroadcasts.remove(userId, packageName);
} else {
mPendingBroadcasts.addComponent(userId, packageName, componentName);
+ Trace.instant(Trace.TRACE_TAG_PACKAGE_MANAGER, "setEnabledSetting broadcast: "
+ + componentName + ": " + setting.getEnabledState());
scheduleBroadcastMessage = true;
}
}
@@ -4095,7 +4101,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final long broadcastDelay = SystemClock.uptimeMillis() > mServiceStartWithDelay
? BROADCAST_DELAY
: BROADCAST_DELAY_DURING_STARTUP;
- mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, broadcastDelay);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(SEND_PENDING_BROADCAST,
+ "component_state_changed" /* obj */), broadcastDelay);
}
}
}
@@ -4113,7 +4120,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final int packageUid = UserHandle.getUid(
userId, pkgSettings.get(packageName).getAppId());
mBroadcastHelper.sendPackageChangedBroadcast(newSnapshot, packageName,
- false /* dontKillApp */, components, packageUid, null /* reason */);
+ false /* dontKillApp */, components, packageUid, null /* reason */,
+ "component_state_changed" /* reasonForTrace */);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -4339,7 +4347,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
true /* dontKillApp */,
new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
pkg.getUid(),
- Intent.ACTION_OVERLAY_CHANGED);
+ Intent.ACTION_OVERLAY_CHANGED, "overlay_changed" /* reasonForTrace */);
}
}, overlayFilter);
@@ -4698,10 +4706,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService
extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
- packageName, extras,
- Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- userIds, null, broadcastAllowList, null,
- null);
+ packageName, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY,
+ null /* targetPkg */, null /* finishedReceiver */, userIds,
+ null /* instantUserIds */, broadcastAllowList,
+ null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
});
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras, userIds, null /* instantUserIds */,
@@ -5858,6 +5867,67 @@ public class PackageManagerService implements PackageSender, TestUtilityService
userId, callingPackage);
}
+ @Override
+ public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ int settingsMode = enabled
+ ? ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED
+ : ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+ PackageStateMutator.Result result =
+ commitPackageStateMutation(
+ null,
+ packageName,
+ packageState ->
+ packageState
+ .setPageSizeAppCompatFlags(settingsMode));
+ if (result.isSpecificPackageNull()) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ scheduleWriteSettings();
+ }
+
+ @Override
+ public boolean isPageSizeCompatEnabled(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int userId = UserHandle.getCallingUserId();
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ PackageStateInternal packageState =
+ snapshotComputer().getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+
+ return packageState == null ? false : packageState.isPageSizeAppCompatEnabled();
+ }
+
+ @Override
+ public String getPageSizeCompatWarningMessage(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int userId = UserHandle.getCallingUserId();
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ PackageStateInternal packageState =
+ snapshotComputer().getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+
+ return packageState == null
+ ? null
+ : packageState.getPageSizeCompatWarningMessage(mContext);
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USERS)
@Override
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
@@ -6305,7 +6375,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
if (pkgUserState != null && pkgUserState.isInstalled()) {
final int packageUid = UserHandle.getUid(userIds[i], appId);
mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
- true /* dontKillApp */, components, packageUid, reason);
+ true /* dontKillApp */, components, packageUid, reason,
+ "mime_group_changed" /* reasonForTrace */);
}
}
});
@@ -6562,6 +6633,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Override
+ @NonNull
+ public List<String> getAllApexDirectories() {
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "getAllApexDirectories can only be called by system or root");
+ List<String> apexDirectories = new ArrayList<>();
+ List<ApexManager.ActiveApexInfo> apexes = mApexManager.getActiveApexInfos();
+ for (int i = 0; i < apexes.size(); i++) {
+ ApexManager.ActiveApexInfo apex = apexes.get(i);
+ apexDirectories.add(apex.apexDirectory.getAbsolutePath());
+ }
+ return apexDirectories;
+ }
+
+ @Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
@@ -7156,17 +7241,17 @@ public class PackageManagerService implements PackageSender, TestUtilityService
// Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
- packageName, extras,
- flags, null, null,
- userIds, null, broadcastAllowList, null,
- null);
+ packageName, extras, flags, null /* targetPkg */,
+ null /* finishedReceiver */, userIds, null /* instantUserIds */,
+ broadcastAllowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */, null /* requiredPermissions */);
});
} else {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
- packageName, extras,
- flags, null, null,
- userIds, null, broadcastAllowList, null,
- null);
+ packageName, extras, flags, null /* targetPkg */,
+ null /* finishedReceiver */, userIds, null /* instantUserIds */,
+ broadcastAllowList, null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras, userIds, null /* instantUserIds */,
@@ -8086,8 +8171,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mRemovePackageHelper.cleanUpForMoveInstall(volumeUuid, packageName, fromCodePath);
}
- void sendPendingBroadcasts() {
- mInstallPackageHelper.sendPendingBroadcasts();
+ void sendPendingBroadcasts(String reasonForTrace) {
+ mInstallPackageHelper.sendPendingBroadcasts(reasonForTrace);
}
void handlePackagePostInstall(@NonNull InstallRequest request, boolean launchedForRestore) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 975758241e77..7af39f74d0d6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -749,7 +749,7 @@ public class PackageManagerServiceUtils {
null /*abiOverride*/, false /*isIncremental*/);
} catch (IOException e) {
logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
- + "; pkg: " + packageName);
+ + "; pkg: " + packageName + "; err: " + e.getMessage());
return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
} finally {
IoUtils.closeQuietly(handle);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7a53fe78c1bf..aa235c2258ac 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3599,6 +3599,13 @@ class PackageManagerShellCommand extends ShellCommand {
.setCompilerFilter(sessionParams.dexoptCompilerFilter)
.build();
break;
+ case "--disable-auto-install-dependencies":
+ if (Flags.sdkDependencyInstaller()) {
+ sessionParams.setAutoInstallDependenciesEnabled(false);
+ } else {
+ throw new IllegalArgumentException("Unknown option " + opt);
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4805,6 +4812,10 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" https://source.android.com/docs/core/runtime/configure"
+ "#compiler_filters");
pw.println(" or 'skip'");
+ if (Flags.sdkDependencyInstaller()) {
+ pw.println(" --disable-auto-install-dependencies: if set, any missing shared");
+ pw.println(" library dependencies will not be auto-installed");
+ }
pw.println("");
pw.println(" install-existing [--user USER_ID|all|current]");
pw.println(" [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index cf5de897cf5d..ef49f49cf040 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -34,10 +34,12 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -45,7 +47,8 @@ import java.util.function.BiFunction;
/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
* used by PackageMonitor to improve the broadcast latency. */
-class PackageMonitorCallbackHelper {
+@VisibleForTesting
+public class PackageMonitorCallbackHelper {
private static final boolean DEBUG = false;
private static final String TAG = "PackageMonitorCallbackHelper";
@@ -71,22 +74,16 @@ class PackageMonitorCallbackHelper {
}
public void onUserRemoved(int userId) {
- ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = null;
+ final ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = new ArrayList<>();
synchronized (mLock) {
- int registerCount = mCallbacks.getRegisteredCallbackCount();
- for (int i = 0; i < registerCount; i++) {
- RegisterUser registerUser =
- (RegisterUser) mCallbacks.getRegisteredCallbackCookie(i);
+ mCallbacks.broadcast((callback, user) -> {
+ RegisterUser registerUser = (RegisterUser) user;
if (registerUser.getUserId() == userId) {
- IRemoteCallback callback = mCallbacks.getRegisteredCallbackItem(i);
- if (targetUnRegisteredCallbacks == null) {
- targetUnRegisteredCallbacks = new ArrayList<>();
- }
targetUnRegisteredCallbacks.add(callback);
}
- }
+ });
}
- if (targetUnRegisteredCallbacks != null && targetUnRegisteredCallbacks.size() > 0) {
+ if (!targetUnRegisteredCallbacks.isEmpty()) {
int count = targetUnRegisteredCallbacks.size();
for (int i = 0; i < count; i++) {
unregisterPackageMonitorCallback(targetUnRegisteredCallbacks.get(i));
@@ -200,21 +197,13 @@ class PackageMonitorCallbackHelper {
private void doNotifyCallbacksByIntent(Intent intent, int userId,
int[] broadcastAllowList, Handler handler) {
- RemoteCallbackList<IRemoteCallback> callbacks;
- synchronized (mLock) {
- callbacks = mCallbacks;
- }
- doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+ doNotifyCallbacks(intent, userId, broadcastAllowList, handler,
null /* filterExtrasFunction */);
}
private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
SparseArray<int[]> broadcastAllowList, Handler handler,
BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
- RemoteCallbackList<IRemoteCallback> callbacks;
- synchronized (mLock) {
- callbacks = mCallbacks;
- }
for (int userId : userIds) {
final Intent intent = new Intent(action,
pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
@@ -230,48 +219,58 @@ class PackageMonitorCallbackHelper {
final int[] allowUids =
broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
- doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
+ doNotifyCallbacks(intent, userId, allowUids, handler, filterExtrasFunction);
}
}
- private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
- Intent intent, int userId, int[] allowUids, Handler handler,
+ private void doNotifyCallbacks(Intent intent, int userId, int[] allowUids, Handler handler,
BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
- handler.post(() -> callbacks.broadcast((callback, user) -> {
- RegisterUser registerUser = (RegisterUser) user;
- if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
- != userId)) {
- return;
- }
- int registerUid = registerUser.getUid();
- if (allowUids != null && registerUid != Process.SYSTEM_UID
- && !ArrayUtils.contains(allowUids, registerUid)) {
- if (DEBUG) {
- Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
- + ", uid " + registerUid);
- }
- return;
- }
- Intent newIntent = intent;
- if (filterExtrasFunction != null) {
- final Bundle extras = intent.getExtras();
- if (extras != null) {
- final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
- if (filteredExtras == null) {
- // caller is unable to access this intent
+ handler.post(() -> {
+ final ArrayList<Pair<IRemoteCallback, Intent>> target = new ArrayList<>();
+ synchronized (mLock) {
+ mCallbacks.broadcast((callback, user) -> {
+ RegisterUser registerUser = (RegisterUser) user;
+ if ((registerUser.getUserId() != UserHandle.USER_ALL)
+ && (registerUser.getUserId() != userId)) {
+ return;
+ }
+ int registerUid = registerUser.getUid();
+ if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
+ && !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
- Slog.w(TAG,
- "Skip invoke PackageMonitorCallback for " + intent.getAction()
- + " because null filteredExtras");
+ Slog.w(TAG, "Skip invoke PackageMonitorCallback for "
+ + intent.getAction() + ", uid " + registerUid);
}
return;
}
- newIntent = new Intent(newIntent);
- newIntent.replaceExtras(filteredExtras);
- }
+ Intent newIntent = intent;
+ if (filterExtrasFunction != null) {
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ final Bundle filteredExtras =
+ filterExtrasFunction.apply(registerUid, extras);
+ if (filteredExtras == null) {
+ // caller is unable to access this intent
+ if (DEBUG) {
+ Slog.w(TAG,
+ "Skip invoke PackageMonitorCallback for "
+ + intent.getAction()
+ + " because null filteredExtras");
+ }
+ return;
+ }
+ newIntent = new Intent(newIntent);
+ newIntent.replaceExtras(filteredExtras);
+ }
+ }
+ target.add(new Pair<>(callback, newIntent));
+ });
+ }
+ for (int i = 0; i < target.size(); i++) {
+ Pair<IRemoteCallback, Intent> p = target.get(i);
+ invokeCallback(p.first, p.second);
}
- invokeCallback(callback, newIntent);
- }));
+ });
}
private void invokeCallback(IRemoteCallback callback, Intent intent) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9428de700385..fb16b862b275 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
@@ -221,6 +222,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
/** @see PackageState#getCategoryOverride() */
private int categoryOverride = ApplicationInfo.CATEGORY_UNDEFINED;
+ private int mPageSizeAppCompatFlags = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
@NonNull
private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
@@ -863,6 +866,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
}
copyMimeGroups(other.mimeGroups);
+ mPageSizeAppCompatFlags = other.mPageSizeAppCompatFlags;
+
pkgState.updateFrom(other.pkgState);
onChanged();
}
@@ -1617,6 +1622,34 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return this;
}
+ /**
+ * @see Set page size app compat mode.
+ */
+ public PackageSetting setPageSizeAppCompatFlags(int mode) {
+ if (mode < 0 || mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MAX) {
+ throw new IllegalArgumentException("Invalid page size compat mode specified");
+ }
+
+ // OR assignment is used here to avoid overriding the mode set by the manifest.
+ this.mPageSizeAppCompatFlags |= mode;
+
+ // Only one bit of the following can be set at same time. Both are needed to detect app
+ // compat 'disabled' state from settings vs bit was never set.
+ if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED == mode) {
+ this.mPageSizeAppCompatFlags &=
+ ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+ } else if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED == mode) {
+ this.mPageSizeAppCompatFlags &=
+ ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+ }
+ onChanged();
+ return this;
+ }
+
+ public int getPageSizeAppCompatFlags() {
+ return mPageSizeAppCompatFlags;
+ }
+
public PackageSetting setLegacyNativeLibraryPath(
String legacyNativeLibraryPathString) {
this.legacyNativeLibraryPath = legacyNativeLibraryPathString;
@@ -1787,6 +1820,63 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return getBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP);
}
+ /** Returns true if ELF files will be loaded in Page size compatibility mode */
+ @Override
+ public boolean isPageSizeAppCompatEnabled() {
+ // If manifest or settings has disabled the compat mode, don't run app in compat mode.
+ boolean manifestOverrideDisabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED) != 0;
+ boolean settingsOverrideDisabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED) != 0;
+ if (manifestOverrideDisabled || settingsOverrideDisabled) {
+ return false;
+ }
+
+ int mask =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED
+ | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED
+ | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+ return (mPageSizeAppCompatFlags & mask) != 0;
+ }
+
+ /**
+ * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+ * alignment.
+ */
+ @Override
+ public String getPageSizeCompatWarningMessage(Context context) {
+ boolean manifestOverrideEnabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+ boolean settingsOverrideEnabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+ if (manifestOverrideEnabled || settingsOverrideEnabled) {
+ return null;
+ }
+
+ boolean uncompressedLibsNotAligned = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED) != 0;
+ boolean elfNotAligned = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED) != 0;
+
+ if (uncompressedLibsNotAligned && elfNotAligned) {
+ return context.getText(
+ com.android.internal.R.string.page_size_compat_apk_and_elf_warning)
+ .toString();
+ }
+
+ if (uncompressedLibsNotAligned) {
+ return context.getText(com.android.internal.R.string.page_size_compat_apk_warning)
+ .toString();
+ }
+
+ if (elfNotAligned) {
+ return context.getText(com.android.internal.R.string.page_size_compat_elf_warning)
+ .toString();
+ }
+
+ return null;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1952,7 +2042,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index f9a8968b8ad6..f01a74e8d60d 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -258,9 +258,10 @@ final class RemovePackageHelper {
*/
public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) {
final String packageName = ps.getPackageName();
- // Step 1: always destroy app profiles.
- mAppDataHelper.destroyAppProfilesLIF(packageName);
-
+ // Step 1: always destroy app profiles except when explicitly preserved
+ if ((flags & Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES) == 0) {
+ mAppDataHelper.destroyAppProfilesLIF(packageName);
+ }
final AndroidPackage pkg;
final SharedUserSetting sus;
synchronized (mPm.mLock) {
@@ -277,7 +278,8 @@ final class RemovePackageHelper {
resolvedPkg = PackageImpl.buildFakeForDeletion(packageName, ps.getVolumeUuid());
}
- int appDataDeletionFlags = FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL;
+ int appDataDeletionFlags = FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | (flags & Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
// Personal data is preserved if the DELETE_KEEP_DATA flag is on
if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
if ((flags & PackageManager.DELETE_ARCHIVE) != 0) {
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index 9a7ba0f082ea..ec91da90729b 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -45,6 +45,7 @@ import android.util.Printer;
import android.util.Slog;
import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedMainComponentImpl;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.IntentResolver;
import com.android.server.LocalServices;
@@ -212,6 +213,7 @@ public class SaferIntentUtils {
* CTS tests. The code in this method shall properly avoid control flows using these arguments.
*/
public static void blockNullAction(IntentArgs args, List componentList) {
+ if (args.intent.getAction() != null) return;
if (ActivityManager.canAccessUnexportedComponents(args.callingUid)) return;
final Computer computer = (Computer) args.snapshot;
@@ -234,14 +236,11 @@ public class SaferIntentUtils {
}
final ParsedMainComponent comp = infoToComponent(
resolveInfo.getComponentInfo(), resolver, args.isReceiver);
- if (comp != null && !comp.getIntents().isEmpty()
- && args.intent.getAction() == null) {
+ if (comp != null && !comp.getIntents().isEmpty()) {
match = false;
}
} else if (c instanceof IntentFilter) {
- if (args.intent.getAction() == null) {
- match = false;
- }
+ match = false;
}
if (!match) {
@@ -265,6 +264,114 @@ public class SaferIntentUtils {
*/
public static void enforceIntentFilterMatching(
IntentArgs args, List<ResolveInfo> resolveInfos) {
+ // Switch to the new intent matching logic if the feature flag is enabled.
+ // Otherwise, use the existing AppCompat based implementation.
+ if (Flags.enableIntentMatchingFlags()) {
+ enforceIntentFilterMatchingWithIntentMatchingFlags(args, resolveInfos);
+ } else {
+ enforceIntentFilterMatchingWithAppCompat(args, resolveInfos);
+ }
+ }
+
+ /**
+ * This version of the method is implemented in Android B and uses "IntentMatchingFlags"
+ */
+ private static void enforceIntentFilterMatchingWithIntentMatchingFlags(
+ IntentArgs args, List<ResolveInfo> resolveInfos) {
+ if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
+ // Do not enforce filter matching when the caller is system or root
+ if (ActivityManager.canAccessUnexportedComponents(args.callingUid)) return;
+
+ final Computer computer = (Computer) args.snapshot;
+ final ComponentResolverApi resolver = computer.getComponentResolver();
+
+ final Printer logPrinter = DEBUG_INTENT_MATCHING
+ ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
+ : null;
+
+ for (int i = resolveInfos.size() - 1; i >= 0; --i) {
+ final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
+
+ // Skip filter matching when the caller is targeting the same app
+ if (UserHandle.isSameApp(args.callingUid, info.applicationInfo.uid)) {
+ continue;
+ }
+
+ final ParsedMainComponent comp = infoToComponent(info, resolver, args.isReceiver);
+
+ if (comp == null || comp.getIntents().isEmpty()) {
+ continue;
+ }
+
+ boolean enforceIntentFilter = Flags.enableIntentMatchingFlags();
+ boolean allowNullAction = false;
+
+ if (Flags.enableIntentMatchingFlags()) {
+ int flags = comp.getIntentMatchingFlags();
+ if (flags == 0 || (flags & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE)
+ == ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE
+ || (flags
+ & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER)
+ == 0) {
+ enforceIntentFilter = false;
+ }
+ if ((flags & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION)
+ == ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION) {
+ allowNullAction = true;
+ }
+ }
+
+ boolean hasNullAction = args.intent.getAction() == null;
+ boolean intentMatchesComponent = false;
+
+ for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
+ IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
+ if (IntentResolver.intentMatchesFilter(
+ intentFilter, args.intent, args.resolvedType)) {
+ intentMatchesComponent = true;
+ break;
+ }
+ }
+
+ boolean blockIntent = false;
+ if (enforceIntentFilter) {
+ if ((hasNullAction && !allowNullAction) || !intentMatchesComponent) {
+ blockIntent = true;
+ }
+ }
+
+ if (hasNullAction) {
+ args.reportEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH, blockIntent);
+ } else if (!intentMatchesComponent) {
+ args.reportEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
+ blockIntent);
+ }
+
+ if (Flags.enforceIntentFilterMatch() && (hasNullAction || !intentMatchesComponent)) {
+ args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+ }
+
+ if (blockIntent) {
+ Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
+ Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+ if (DEBUG_INTENT_MATCHING) {
+ Slog.v(TAG, "Component intent filters:");
+ comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, " "));
+ Slog.v(TAG, "-----------------------------");
+ }
+ resolveInfos.remove(i);
+ }
+ }
+ }
+
+ /**
+ * This version of the method is implemented in Android V and uses "AppCompat"
+ */
+ private static void enforceIntentFilterMatchingWithAppCompat(
+ IntentArgs args, List<ResolveInfo> resolveInfos) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
// Do not enforce filter matching when the caller is system or root
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0802e9ee50bc..5c8042007ec4 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -52,6 +52,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
@@ -63,6 +64,8 @@ import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -72,7 +75,6 @@ import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
import android.util.jar.StrictJarFile;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ComponentMutateUtils;
@@ -105,6 +107,9 @@ import java.util.UUID;
* Helper class that handles package scanning logic
*/
final class ScanPackageUtils {
+
+ public static final int PAGE_SIZE_16KB = 16384;
+
/**
* Just scans the package without any side effects.
*
@@ -114,10 +119,9 @@ final class ScanPackageUtils {
* @param currentTime The current time, in millis
* @return The results of the scan
*/
- @GuardedBy("mPm.mInstallLock")
@VisibleForTesting
@NonNull
- public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
+ public static ScanResult scanPackageOnly(@NonNull ScanRequest request,
PackageManagerServiceInjector injector,
boolean isUnderFactoryTest, long currentTime)
throws PackageManagerException {
@@ -418,6 +422,37 @@ final class ScanPackageUtils {
+ " abiOverride=" + pkgSetting.getCpuAbiOverride());
}
+ boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == PAGE_SIZE_16KB;
+ if (Flags.appCompatOption16kb() && is16KbDevice) {
+ // Alignment checks are used decide whether this app should run in compat mode when
+ // nothing was specified in manifest. Manifest should always take precedence over
+ // something decided by platform.
+ if (parsedPackage.getPageSizeAppCompatFlags()
+ > ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ pkgSetting.setPageSizeAppCompatFlags(parsedPackage.getPageSizeAppCompatFlags());
+ } else {
+ // 16 KB is only support for 64 bit ABIs and for apps which are being installed
+ // Check alignment. System, Apex and Platform packages should be page-agnostic now
+ if ((Build.SUPPORTED_64_BIT_ABIS.length > 0)
+ && !isSystemApp
+ && !isApex
+ && !isPlatformPackage) {
+ int mode =
+ packageAbiHelper.checkPackageAlignment(
+ parsedPackage,
+ pkgSetting.getLegacyNativeLibraryPath(),
+ parsedPackage.isNativeLibraryRootRequiresIsa(),
+ pkgSetting.getCpuAbiOverride());
+ if (mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ pkgSetting.setPageSizeAppCompatFlags(mode);
+ } else {
+ Slog.e(TAG, "Error occurred while checking alignment of package : "
+ + parsedPackage.getPackageName());
+ }
+ }
+ }
+ }
+
if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) {
// We don't do this here during boot because we can do it all
// at once after scanning all existing packages.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1f672a093b38..485a28070bc5 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3313,6 +3313,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
if (pkg.getBaseRevisionCode() != 0) {
serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
}
+ if (pkg.getPageSizeAppCompatFlags()
+ != ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ serializer.attributeInt(null, "pageSizeCompat", pkg.getPageSizeAppCompatFlags());
+ }
+
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
@@ -4129,6 +4134,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
boolean isScannedAsStoppedSystemApp = false;
boolean isSdkLibrary = false;
int baseRevisionCode = 0;
+ int PageSizeCompat = 0;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -4175,6 +4181,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
PackageManager.APP_METADATA_SOURCE_UNKNOWN);
baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
+ PageSizeCompat = parser.getAttributeInt(null, "pageSizeCompat",
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
"scannedAsStoppedSystemApp", false);
@@ -4330,7 +4338,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
.setTargetSdkVersion(targetSdkVersion)
.setBaseRevisionCode(baseRevisionCode)
.setRestrictUpdateHash(restrictUpdateHash)
- .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
+ .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp)
+ .setPageSizeAppCompatFlags(PageSizeCompat);
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -5211,6 +5220,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pw.print(" (override=true)");
}
pw.println();
+ pw.print(prefix);
+ pw.print(" pageSizeCompat=");
+ pw.print(ps.getPageSizeAppCompatFlags());
+ pw.println();
if (!ps.getPkg().getQueriesPackages().isEmpty()) {
pw.append(prefix).append(" queriesPackages=")
.println(ps.getPkg().getQueriesPackages());
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 929fccce5265..17d7a14d9129 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -33,6 +33,7 @@ import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.PackageLite;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -83,6 +84,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
private static final boolean DEBUG_SHARED_LIBRARIES = false;
private static final String LIBRARY_TYPE_SDK = "sdk";
+ private static final String LIBRARY_TYPE_STATIC = "static shared";
/**
* Apps targeting Android S and above need to declare dependencies to the public native
@@ -926,18 +928,19 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
if (!pkg.getUsesLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
- availablePackages, newLibraries);
+ availablePackages, newLibraries, null);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
- null, pkg.getPackageName(), "static shared", true,
- pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
+ null, pkg.getPackageName(), LIBRARY_TYPE_STATIC, true,
+ pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries,
+ null);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ usesLibraryInfos, availablePackages, newLibraries, null);
}
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
pkg.getPackageName(), pkg.getTargetSdkVersion())) {
@@ -945,13 +948,13 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
null, null, pkg.getPackageName(), "native shared", true,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
- newLibraries);
+ newLibraries, null);
}
if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
null, null, null, pkg.getPackageName(), "native shared", false,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
- newLibraries);
+ newLibraries, null);
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
@@ -961,11 +964,34 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
pkg.getUsesSdkLibrariesOptional(),
pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ usesLibraryInfos, availablePackages, newLibraries, null);
}
return usesLibraryInfos;
}
+ List<SharedLibraryInfo> collectMissingSharedLibraryInfos(PackageLite pkgLite)
+ throws PackageManagerException {
+ ArrayList<SharedLibraryInfo> missingSharedLibrary = new ArrayList<>();
+ synchronized (mPm.mLock) {
+ collectSharedLibraryInfos(pkgLite.getUsesSdkLibraries(),
+ pkgLite.getUsesSdkLibrariesVersionsMajor(),
+ pkgLite.getUsesSdkLibrariesCertDigests(),
+ /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_SDK,
+ /*required=*/ true, pkgLite.getTargetSdk(),
+ /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+ missingSharedLibrary);
+
+ collectSharedLibraryInfos(pkgLite.getUsesStaticLibraries(),
+ pkgLite.getUsesStaticLibrariesVersions(),
+ pkgLite.getUsesStaticLibrariesCertDigests(),
+ /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_STATIC,
+ /*required=*/ true, pkgLite.getTargetSdk(),
+ /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+ missingSharedLibrary);
+ }
+ return missingSharedLibrary;
+ }
+
private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
@NonNull List<String> requestedLibraries,
@Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@@ -973,7 +999,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
@NonNull String packageName, @NonNull String libraryType, boolean required,
int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
@NonNull final Map<String, AndroidPackage> availablePackages,
- @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+ @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+ @Nullable final List<SharedLibraryInfo> outMissingSharedLibraryInfos)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
@@ -986,16 +1013,34 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
libName, libVersion, mSharedLibraries, newLibraries);
}
if (libraryInfo == null) {
- // Only allow app be installed if the app specifies the sdk-library dependency is
- // optional
- if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
- && !libsOptional[i]))) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires unavailable " + libraryType
- + " library " + libName + "; failing!");
- } else if (DEBUG_SHARED_LIBRARIES) {
- Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
- + " library " + libName + "; ignoring!");
+ if (required) {
+ boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
+ || libraryType.equals(LIBRARY_TYPE_STATIC);
+ if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
+ // If Dependency Installation is supported, try that instead of failing.
+ final List<String> libCertDigests = Arrays.asList(requiredCertDigests[i]);
+ SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
+ libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE,
+ libCertDigests
+ );
+ outMissingSharedLibraryInfos.add(missingLibrary);
+ } else {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable " + libraryType
+ + " library " + libName + "; failing!");
+ }
+ } else {
+ // Only allow app be installed if the app specifies the sdk-library
+ // dependency is optional
+ boolean isOptional = libsOptional != null && libsOptional[i];
+ if (LIBRARY_TYPE_SDK.equals(libraryType) && !isOptional) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable " + libraryType
+ + " library " + libName + "; failing!");
+ } else if (DEBUG_SHARED_LIBRARIES) {
+ Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+ + " library " + libName + "; ignoring!");
+ }
}
} else {
if (requiredVersions != null && requiredCertDigests != null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5518bfae8277..1052c94d7799 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1022,7 +1022,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Close.
file.finishWrite(outs);
} catch (IOException e) {
- Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+ Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
file.failWrite(outs);
}
}
@@ -1055,7 +1055,7 @@ public class ShortcutService extends IShortcutService.Stub {
final String tag = parser.getName();
if (depth == 1) {
if (!TAG_ROOT.equals(tag)) {
- Slog.e(TAG, "Invalid root tag: " + tag);
+ Slog.v(TAG, "Invalid root tag: " + tag);
return;
}
continue;
@@ -1066,7 +1066,7 @@ public class ShortcutService extends IShortcutService.Stub {
mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE));
break;
default:
- Slog.e(TAG, "Invalid tag: " + tag);
+ Slog.v(TAG, "Invalid tag: " + tag);
break;
}
}
@@ -1113,7 +1113,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Remove all dangling bitmap files.
cleanupDanglingBitmapDirectoriesLocked(userId);
} catch (XmlPullParserException | IOException e) {
- Slog.e(TAG, "Failed to write to file " + file, e);
+ Slog.w(TAG, "Failed to write to file " + file, e);
file.failWrite(os);
}
}
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 951986fbd71a..a09d4776d986 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -227,7 +227,7 @@ public final class StorageEventHelper extends StorageEventListener {
}
if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded);
- mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm::snapshotComputer,
true /* mediaStatus */, false /* replacing */, loaded);
synchronized (mLoadedVolumes) {
mLoadedVolumes.add(vol.getId());
@@ -279,7 +279,7 @@ public final class StorageEventHelper extends StorageEventListener {
}
if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded);
- mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm::snapshotComputer,
false /* mediaStatus */, false /* replacing */, unloaded);
synchronized (mLoadedVolumes) {
mLoadedVolumes.remove(vol.getId());
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 4e70cc52ef90..88fd1aa159d3 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -192,21 +192,20 @@ public final class SuspendPackageHelper {
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
if (!notifyPackagesList.isEmpty()) {
final String[] changedPackages =
notifyPackagesList.toArray(new String[0]);
- mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+ mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(mPm::snapshotComputer,
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, notifyUids.toArray(), quarantined, targetUserId);
- mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages,
- suspended, targetUserId);
+ mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(mPm::snapshotComputer,
+ changedPackages, suspended, targetUserId);
mPm.scheduleWritePackageRestrictions(targetUserId);
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!changedPackagesList.isEmpty()) {
- mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+ mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(mPm::snapshotComputer,
Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined,
targetUserId);
@@ -343,13 +342,12 @@ public final class SuspendPackageHelper {
});
mPm.scheduleWritePackageRestrictions(targetUserId);
- final Computer newSnapshot = mPm.snapshotComputer();
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
- mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray,
- false, targetUserId);
- mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+ mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(mPm::snapshotComputer,
+ packageArray, false, targetUserId);
+ mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(mPm::snapshotComputer,
Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), false, targetUserId);
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 21a6df203015..1fda4782fc86 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -101,6 +101,22 @@
]
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"file_patterns": [
"core/java/.*Install.*",
@@ -117,6 +133,22 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"file_patterns": [
"core/java/.*Install.*",
@@ -133,6 +165,22 @@
]
},
{
+ "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUpdateSelfTestCases",
"file_patterns": [
"core/java/.*Install.*",
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 041f2d3a459d..04ce4e692fef 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -68,6 +68,10 @@ class UserDataPreparer {
void prepareUserData(UserInfo userInfo, int flags) {
try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ if (storage == null) {
+ Log.e(TAG, "prepareUserData failed due to unable get StorageManager");
+ return;
+ }
/*
* Internal storage must be prepared before adoptable storage, since the user's volume
* keys are stored in their internal storage.
@@ -159,14 +163,16 @@ class UserDataPreparer {
void destroyUserData(int userId, int flags) {
try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
- /*
- * Volume destruction order isn't really important, but to avoid any weird issues we
- * process internal storage last, the opposite of prepareUserData.
- */
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- if (volumeUuid != null) {
- destroyUserDataLI(volumeUuid, userId, flags);
+ if (storage != null) {
+ /*
+ * Volume destruction order isn't really important, but to avoid any weird issues we
+ * process internal storage last, the opposite of prepareUserData.
+ */
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ if (volumeUuid != null) {
+ destroyUserDataLI(volumeUuid, userId, flags);
+ }
}
}
destroyUserDataLI(null /* internal storage */, userId, flags);
@@ -194,9 +200,10 @@ class UserDataPreparer {
}
}
- // All the user's data directories should be empty now, so finish the job.
- storage.destroyUserStorage(volumeUuid, userId, flags);
-
+ if (storage != null) {
+ // All the user's data directories should be empty now, so finish the job.
+ storage.destroyUserStorage(volumeUuid, userId, flags);
+ }
} catch (Exception e) {
logCriticalInfo(Log.WARN,
"Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8bab9de903ba..066fce068d61 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.EXTRA_USER_ID;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.LauncherUserInfo.PRIVATE_SPACE_ENTRYPOINT_HIDDEN;
import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
@@ -32,6 +33,7 @@ import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -195,6 +197,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
@@ -340,6 +343,10 @@ public class UserManagerService extends IUserManager.Stub {
private static final String TRON_USER_CREATED = "users_user_created";
private static final String TRON_DEMO_CREATED = "users_demo_created";
+ // The boot user strategy for HSUM.
+ private static final int BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER = 0;
+ private static final int BOOT_TO_HSU_FOR_PROVISIONED_DEVICE = 1;
+
private final Context mContext;
private final PackageManagerService mPm;
@@ -1101,23 +1108,14 @@ public class UserManagerService extends IUserManager.Stub {
if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
UserManager.invalidateIsUserUnlockedCache();
UserManager.invalidateQuietModeEnabledCache();
- UserManager.invalidateUserSerialNumberCache();
+ if (android.multiuser.Flags.cacheUserPropertiesCorrectlyReadOnly()) {
+ UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
+ }
+ UserManager.invalidateCacheOnUserListChange();
}
}
- private boolean doesDeviceHardwareSupportPrivateSpace() {
- return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
- && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
- && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
- && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
- }
-
- private static boolean isAutoLockForPrivateSpaceEnabled() {
- return android.os.Flags.allowPrivateProfile()
- && Flags.supportAutolockForPrivateSpace()
- && android.multiuser.Flags.enablePrivateSpaceFeatures();
- }
-
void systemReady() {
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -1162,6 +1160,19 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ private boolean doesDeviceHardwareSupportPrivateSpace() {
+ return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
+ && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
+ && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
+ && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
+ }
+
+ private static boolean isAutoLockForPrivateSpaceEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.supportAutolockForPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
+ }
+
private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
return android.os.Flags.allowPrivateProfile()
&& android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts()
@@ -1217,7 +1228,7 @@ public class UserManagerService extends IUserManager.Stub {
// Mark the user for removal.
addRemovingUserIdLocked(ui.id);
ui.partial = true;
- ui.flags |= UserInfo.FLAG_DISABLED;
+ addUserInfoFlags(ui, UserInfo.FLAG_DISABLED);
}
/* Prunes out any partially created or partially removed users. */
@@ -1259,7 +1270,7 @@ public class UserManagerService extends IUserManager.Stub {
if (ui.preCreated) {
preCreatedUsers.add(ui);
addRemovingUserIdLocked(ui.id);
- ui.flags |= UserInfo.FLAG_DISABLED;
+ addUserInfoFlags(ui, UserInfo.FLAG_DISABLED);
ui.partial = true;
}
}
@@ -1386,37 +1397,77 @@ public class UserManagerService extends IUserManager.Stub {
}
if (isHeadlessSystemUserMode()) {
- if (mContext.getResources()
- .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
- return UserHandle.USER_SYSTEM;
- }
- // Return the previous foreground user, if there is one.
- final int previousUser = getPreviousFullUserToEnterForeground();
- if (previousUser != UserHandle.USER_NULL) {
- Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
- return previousUser;
- }
- // No previous user. Return the first switchable user if there is one.
- synchronized (mUsersLock) {
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- final UserData userData = mUsers.valueAt(i);
- if (userData.info.supportsSwitchToByUser()) {
- int firstSwitchable = userData.info.id;
- Slogf.i(LOG_TAG,
- "Boot user is first switchable user %d", firstSwitchable);
- return firstSwitchable;
- }
- }
+ final int bootStrategy = mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+ switch (bootStrategy) {
+ case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER:
+ return getPreviousOrFirstSwitchableUser();
+ case BOOT_TO_HSU_FOR_PROVISIONED_DEVICE:
+ return getBootUserBasedOnProvisioning();
+ default:
+ Slogf.w(LOG_TAG, "Unknown HSUM boot strategy: %d", bootStrategy);
+ return getPreviousOrFirstSwitchableUser();
}
- // No switchable users found. Uh oh!
- throw new UserManager.CheckedUserOperationException(
- "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
}
// Not HSUM, return system user.
return UserHandle.USER_SYSTEM;
}
+ private @UserIdInt int getBootUserBasedOnProvisioning()
+ throws UserManager.CheckedUserOperationException {
+ final boolean provisioned = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ if (provisioned) {
+ return UserHandle.USER_SYSTEM;
+ } else {
+ final int firstSwitchableFullUser = getFirstSwitchableUser(true);
+ if (firstSwitchableFullUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable full user %d",
+ firstSwitchableFullUser);
+ return firstSwitchableFullUser;
+ }
+ // No switchable full user found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable full user found", USER_OPERATION_ERROR_UNKNOWN);
+ }
+ }
+
+ private @UserIdInt int getPreviousOrFirstSwitchableUser()
+ throws UserManager.CheckedUserOperationException {
+ // Return the previous foreground user, if there is one.
+ final int previousUser = getPreviousFullUserToEnterForeground();
+ if (previousUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+ return previousUser;
+ }
+ // No previous user. Return the first switchable user if there is one.
+ final int firstSwitchableUser = getFirstSwitchableUser(false);
+ if (firstSwitchableUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable user %d", firstSwitchableUser);
+ return firstSwitchableUser;
+ }
+ // No switchable users found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
+ }
+
+ private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) {
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ if (userData.info.supportsSwitchToByUser() &&
+ (!fullUserOnly || userData.info.isFull())) {
+ int firstSwitchable = userData.info.id;
+ return firstSwitchable;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public int getPreviousFullUserToEnterForeground() {
@@ -2115,7 +2166,7 @@ public class UserManagerService extends IUserManager.Stub {
info = getUserInfoLU(userId);
if (info != null && !info.isEnabled()) {
wasUserDisabled = true;
- info.flags ^= UserInfo.FLAG_DISABLED;
+ removeUserInfoFlags(info, UserInfo.FLAG_DISABLED);
writeUserLP(getUserDataLU(info.id));
}
}
@@ -2125,6 +2176,36 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ /**
+ * This method is for monitoring flag changes on users flags and invalidate cache relevant to
+ * the change. The method add flags and invalidateOnUserInfoFlagChange for the flags which
+ * has changed.
+ * @param userInfo of existing user in mUsers list
+ * @param flags to be added to userInfo
+ */
+ private void addUserInfoFlags(UserInfo userInfo, @UserInfoFlag int flags) {
+ int diff = ~userInfo.flags & flags;
+ if (diff > 0) {
+ userInfo.flags |= diff;
+ UserManager.invalidateOnUserInfoFlagChange(diff);
+ }
+ }
+
+ /**
+ * This method is for monitoring flag changes on users flags and invalidate cache relevant to
+ * the change. The method remove flags and invalidateOnUserInfoFlagChange for the flags which
+ * has changed.
+ * @param userInfo of existing user in mUsers list
+ * @param flags to be removed from userInfo
+ */
+ private void removeUserInfoFlags(UserInfo userInfo, @UserInfoFlag int flags) {
+ int diff = userInfo.flags & flags;
+ if (diff > 0) {
+ userInfo.flags ^= diff;
+ UserManager.invalidateOnUserInfoFlagChange(diff);
+ }
+ }
+
@Override
public void setUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("set user admin");
@@ -4448,7 +4529,7 @@ public class UserManagerService extends IUserManager.Stub {
if (userData != null) {
synchronized (mUsersLock) {
- mUsers.put(userData.info.id, userData);
+ addUserDataLU(userData);
if (mNextSerialNumber < 0
|| mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
@@ -4903,7 +4984,10 @@ public class UserManagerService extends IUserManager.Stub {
res.getValue(com.android.internal.R.string.owner_name, mOwnerNameTypedValue, true);
final CharSequence ownerName = mOwnerNameTypedValue.coerceToString();
mOwnerName.set(ownerName != null ? ownerName.toString() : null);
+ // Invalidate when owners name changes due to config change.
+ UserManager.invalidateCacheOnUserDataChanged();
}
+
}
private void scheduleWriteUserList() {
@@ -4916,6 +5000,8 @@ public class UserManagerService extends IUserManager.Stub {
Message msg = mHandler.obtainMessage(WRITE_USER_LIST_MSG);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
}
+ // Invalidate cache when {@link UserData} changed, but write was scheduled for later.
+ UserManager.invalidateCacheOnUserDataChanged();
}
private void scheduleWriteUser(@UserIdInt int userId) {
@@ -4928,6 +5014,8 @@ public class UserManagerService extends IUserManager.Stub {
Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userId);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
}
+ // Invalidate cache when {@link Data} changed, but write was scheduled for later.
+ UserManager.invalidateCacheOnUserDataChanged();
}
private ResilientAtomicFile getUserFile(int userId) {
@@ -4951,6 +5039,9 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) {
debug("writeUserLP " + userData);
}
+ // invalidate caches related to any {@link UserData} change.
+ UserManager.invalidateCacheOnUserDataChanged();
+
try (ResilientAtomicFile userFile = getUserFile(userData.info.id)) {
FileOutputStream fos = null;
try {
@@ -5115,6 +5206,8 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) {
debug("writeUserList");
}
+ // invalidate caches related to any {@link UserData} change.
+ UserManager.invalidateCacheOnUserDataChanged();
try (ResilientAtomicFile file = getUserListFile()) {
FileOutputStream fos = null;
@@ -5724,7 +5817,7 @@ public class UserManagerService extends IUserManager.Stub {
userData.info = userInfo;
userData.userProperties = new UserProperties(
userTypeDetails.getDefaultUserPropertiesReference());
- mUsers.put(userId, userData);
+ addUserDataLU(userData);
}
writeUserLP(userData);
writeUserListLP();
@@ -5785,6 +5878,9 @@ public class UserManagerService extends IUserManager.Stub {
}
userInfo.partial = false;
+ if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) {
+ UserManager.invalidateCacheOnUserListChange();
+ }
synchronized (mPackagesLock) {
writeUserLP(userData);
}
@@ -6026,8 +6122,11 @@ public class UserManagerService extends IUserManager.Stub {
// If the user switch hasn't been explicitly toggled on or off by the user, turn it on.
if (android.provider.Settings.Global.getString(mContext.getContentResolver(),
android.provider.Settings.Global.USER_SWITCHER_ENABLED) == null) {
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1);
+ if (Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_enableUserSwitcherUponUserCreation)) {
+ android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1);
+ }
}
}
}
@@ -6138,7 +6237,7 @@ public class UserManagerService extends IUserManager.Stub {
final UserData userData = new UserData();
userData.info = userInfo;
synchronized (mUsersLock) {
- mUsers.put(userInfo.id, userData);
+ addUserDataLU(userData);
}
updateUserIds();
return userData;
@@ -6148,8 +6247,7 @@ public class UserManagerService extends IUserManager.Stub {
@VisibleForTesting
void removeUserInfo(@UserIdInt int userId) {
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
}
}
@@ -6238,7 +6336,7 @@ public class UserManagerService extends IUserManager.Stub {
userData.info.guestToRemove = true;
// Mark it as disabled, so that it isn't returned any more when
// profiles are queried.
- userData.info.flags |= UserInfo.FLAG_DISABLED;
+ addUserInfoFlags(userData.info, UserInfo.FLAG_DISABLED);
writeUserLP(userData);
}
} finally {
@@ -6258,14 +6356,16 @@ public class UserManagerService extends IUserManager.Stub {
Slog.i(LOG_TAG, "removeUser u" + userId);
checkCreateUsersPermission("Only the system can remove users");
- final String restriction = getUserRemovalRestriction(userId);
- if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
- Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
+ final Optional<String> restrictionOptional = getUserRemovalRestrictionOptional(userId);
+ if (!restrictionOptional.isEmpty()
+ && getUserRestrictions(UserHandle.getCallingUserId())
+ .getBoolean(restrictionOptional.get(), false)) {
+ Slog.w(LOG_TAG, "Cannot remove user. " + restrictionOptional.get() + " is enabled.");
return false;
}
if (mCurrentBootPhase < SystemService.PHASE_ACTIVITY_MANAGER_READY) {
Slog.w(LOG_TAG, "Cannot remove user, removeUser is called too early during boot. "
- + "ActivityManager is not ready yet.");
+ + "ActivityManager is not ready yet.");
return false;
}
return removeUserWithProfilesUnchecked(userId);
@@ -6332,18 +6432,30 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * Returns the string name of the restriction to check for user removal. The restriction name
- * varies depending on whether the user is a managed profile.
+ * Returns an optional string name of the restriction to check for user removal. The restriction
+ * name varies depending on whether the user is a managed profile.
+ *
+ * <p>If the flag android.multiuser.ignore_restrictions_when_deleting_private_profile is enabled
+ * and the user is a private profile (i.e. has no removal restrictions) the method will return
+ * {@code Optional.empty()}.
*/
- private String getUserRemovalRestriction(@UserIdInt int userId) {
+ private Optional<String> getUserRemovalRestrictionOptional(@UserIdInt int userId) {
+ final boolean isPrivateProfile;
final boolean isManagedProfile;
final UserInfo userInfo;
synchronized (mUsersLock) {
userInfo = getUserInfoLU(userId);
}
+ isPrivateProfile = userInfo != null && userInfo.isPrivateProfile();
isManagedProfile = userInfo != null && userInfo.isManagedProfile();
- return isManagedProfile
- ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
+ if (android.multiuser.Flags.ignoreRestrictionsWhenDeletingPrivateProfile()
+ && isPrivateProfile) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ isManagedProfile
+ ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
+ : UserManager.DISALLOW_REMOVE_USER);
}
private boolean removeUserUnchecked(@UserIdInt int userId) {
@@ -6364,9 +6476,12 @@ public class UserManagerService extends IUserManager.Stub {
// on next startup, in case the runtime stops now before stopping and
// removing the user completely.
userData.info.partial = true;
+ if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) {
+ UserManager.invalidateCacheOnUserListChange();
+ }
// Mark it as disabled, so that it isn't returned any more when
// profiles are queried.
- userData.info.flags |= UserInfo.FLAG_DISABLED;
+ addUserInfoFlags(userData.info, UserInfo.FLAG_DISABLED);
writeUserLP(userData);
}
@@ -6452,9 +6567,13 @@ public class UserManagerService extends IUserManager.Stub {
checkCreateUsersPermission("Only the system can remove users");
if (!overrideDevicePolicy) {
- final String restriction = getUserRemovalRestriction(userId);
- if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
- Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
+ final Optional<String> restrictionOptional = getUserRemovalRestrictionOptional(userId);
+ if (!restrictionOptional.isEmpty()
+ && getUserRestrictions(UserHandle.getCallingUserId())
+ .getBoolean(restrictionOptional.get(), false)) {
+ Slog.w(
+ LOG_TAG,
+ "Cannot remove user. " + restrictionOptional.get() + " is enabled.");
return UserManager.REMOVE_RESULT_ERROR_USER_RESTRICTION;
}
}
@@ -6579,8 +6698,7 @@ public class UserManagerService extends IUserManager.Stub {
// Remove this user from the list
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
mIsUserManaged.delete(userId);
}
synchronized (mUserStates) {
@@ -6969,6 +7087,26 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
+ * Adding user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void addUserDataLU(UserData userData) {
+ if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) {
+ UserManager.invalidateCacheOnUserListChange();
+ }
+ mUsers.put(userData.info.id, userData);
+ }
+
+ /**
+ * Removing user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void removeUserDataLU(@UserIdInt int userId) {
+ UserManager.invalidateCacheOnUserListChange();
+ mUsers.remove(userId);
+ }
+
+ /**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
private void updateUserIds() {
@@ -7742,7 +7880,7 @@ public class UserManagerService extends IUserManager.Stub {
if (userInfo != null && userInfo.isEphemeral()) {
// Do not allow switching back to the ephemeral user again as the user is going
// to be deleted.
- userInfo.flags |= UserInfo.FLAG_DISABLED;
+ addUserInfoFlags(userInfo, UserInfo.FLAG_DISABLED);
if (userInfo.isGuest()) {
// Indicate that the guest will be deleted after it stops.
userInfo.guestToRemove = true;
@@ -7825,11 +7963,25 @@ public class UserManagerService extends IUserManager.Stub {
}
if (userInfo != null) {
final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
- final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
- userDetails.getName(),
- userInfo.serialNumber)
- .build();
- return uiInfo;
+
+ if (Flags.addLauncherUserConfig()) {
+ Bundle config = new Bundle();
+ if (userInfo.isPrivateProfile()) {
+ try {
+ int parentId = getProfileParentIdUnchecked(userId);
+ config.putBoolean(PRIVATE_SPACE_ENTRYPOINT_HIDDEN,
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ HIDE_PRIVATESPACE_ENTRY_POINT, parentId) == 1);
+ } catch (Settings.SettingNotFoundException e) {
+ config.putBoolean(PRIVATE_SPACE_ENTRYPOINT_HIDDEN, false);
+ }
+ }
+ return new LauncherUserInfo.Builder(userDetails.getName(),
+ userInfo.serialNumber, config).build();
+ }
+
+ return new LauncherUserInfo.Builder(userDetails.getName(),
+ userInfo.serialNumber).build();
} else {
return null;
}
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 55afb17614af..c22e382682a7 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -31,6 +31,7 @@ import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.snapshot.PackageDataSnapshot;
@@ -71,8 +72,26 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@NonNull
@Override
public FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user) {
+ return withFilteredSnapshot(callingUid, user, /* uncommittedPs= */ null);
+ }
+
+ /**
+ * Creates a {@link FilteredSnapshot} with a uncommitted {@link PackageState} that is used for
+ * dexopt in the art service to get the correct package state before the package is committed.
+ */
+ @NonNull
+ public static FilteredSnapshotImpl withFilteredSnapshot(PackageManagerLocal pm,
+ @NonNull PackageState uncommittedPs) {
+ return ((PackageManagerLocalImpl) pm).withFilteredSnapshot(Binder.getCallingUid(),
+ Binder.getCallingUserHandle(), uncommittedPs);
+ }
+
+ @NonNull
+ private FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user,
+ @Nullable PackageState uncommittedPs) {
return new FilteredSnapshotImpl(callingUid, user,
- mService.snapshotComputer(false /*allowLiveComputer*/), null);
+ mService.snapshotComputer(/* allowLiveComputer= */ false),
+ /* parentSnapshot= */ null, uncommittedPs);
}
@Override
@@ -145,7 +164,8 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@Override
public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) {
- return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this);
+ return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this,
+ /* uncommittedPs= */ null);
}
@SuppressWarnings("RedundantSuppression")
@@ -209,13 +229,18 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@Nullable
private final UnfilteredSnapshotImpl mParentSnapshot;
+ @Nullable
+ private final PackageState mUncommitPackageState;
+
private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user,
@NonNull PackageDataSnapshot snapshot,
- @Nullable UnfilteredSnapshotImpl parentSnapshot) {
+ @Nullable UnfilteredSnapshotImpl parentSnapshot,
+ @Nullable PackageState uncommittedPs) {
super(snapshot);
mCallingUid = callingUid;
mUserId = user.getIdentifier();
mParentSnapshot = parentSnapshot;
+ mUncommitPackageState = uncommittedPs;
}
@Override
@@ -237,6 +262,10 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@Override
public PackageState getPackageState(@NonNull String packageName) {
checkClosed();
+ if (mUncommitPackageState != null
+ && packageName.equals(mUncommitPackageState.getPackageName())) {
+ return mUncommitPackageState;
+ }
return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
}
@@ -250,6 +279,11 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
var filteredPackageStates = new ArrayMap<String, PackageState>();
for (int index = 0, size = packageStates.size(); index < size; index++) {
var packageState = packageStates.valueAt(index);
+ if (mUncommitPackageState != null
+ && packageState.getPackageName().equals(
+ mUncommitPackageState.getPackageName())) {
+ packageState = (PackageStateInternal) mUncommitPackageState;
+ }
if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
filteredPackageStates.put(packageStates.keyAt(index), packageState);
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 2db454aa4c41..db65bf059319 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -33,6 +33,7 @@ import com.android.internal.pm.parsing.IPackageCacher;
import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.ApexManager;
@@ -41,6 +42,8 @@ import libcore.io.IoUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
public class PackageCacher implements IPackageCacher {
@@ -57,6 +60,8 @@ public class PackageCacher implements IPackageCacher {
@Nullable
private final PackageParser2.Callback mCallback;
+ private static final AconfigFlags sAconfigFlags = ParsingPackageUtils.getAconfigFlags();
+
public PackageCacher(File cacheDir) {
this(cacheDir, null);
}
@@ -136,7 +141,7 @@ public class PackageCacher implements IPackageCacher {
* Given a {@code packageFile} and a {@code cacheFile} returns whether the
* cache file is up to date based on the mod-time of both files.
*/
- private static boolean isCacheUpToDate(File packageFile, File cacheFile) {
+ private static boolean isCacheFileUpToDate(File packageFile, File cacheFile) {
try {
// In case packageFile is located on one of /apex mount points it's mtime will always be
// 0. Instead, we can use mtime of the APEX file backing the corresponding mount point.
@@ -185,16 +190,36 @@ public class PackageCacher implements IPackageCacher {
try {
// If the cache is not up to date, return null.
- if (!isCacheUpToDate(packageFile, cacheFile)) {
+ if (!isCacheFileUpToDate(packageFile, cacheFile)) {
return null;
}
final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
- ParsedPackage parsed = fromCacheEntry(bytes);
+ final ParsedPackage parsed = fromCacheEntry(bytes);
if (!packageFile.getAbsolutePath().equals(parsed.getPath())) {
// Don't use this cache if the path doesn't match
return null;
}
+
+ if (!android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) {
+ return parsed;
+ }
+
+ final Map<String, Boolean> featureFlagState =
+ ((PackageImpl) parsed).getFeatureFlagState();
+ if (!featureFlagState.isEmpty()) {
+ Slog.d(TAG, "Feature flags for package " + packageFile + ": " + featureFlagState);
+ for (var entry : featureFlagState.entrySet()) {
+ final String flagPackageAndName = entry.getKey();
+ if (!Objects.equals(sAconfigFlags.getFlagValue(flagPackageAndName),
+ entry.getValue())) {
+ Slog.i(TAG, "Feature flag " + flagPackageAndName + " changed for package "
+ + packageFile + "; cached result is invalid");
+ return null;
+ }
+ }
+ }
+
return parsed;
} catch (Throwable e) {
Slog.w(TAG, "Error reading package cache: ", e);
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 09ad1384fadd..09feb18d07bf 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -36,6 +36,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.health.connect.HealthPermissions;
import android.media.RingtoneManager;
import android.media.midi.MidiManager;
import android.net.Uri;
@@ -48,6 +49,7 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.permission.PermissionManager;
+import android.permission.flags.Flags;
import android.print.PrintManager;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
@@ -64,6 +66,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -213,8 +216,13 @@ final class DefaultPermissionGrantPolicy {
private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
static {
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEART_RATE);
+ SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+ } else {
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ }
}
private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
@@ -885,10 +893,13 @@ final class DefaultPermissionGrantPolicy {
}
// Allow voice search on wear
- grantPermissionsToSystemPackage(pm,
- getDefaultSystemHandlerActivityPackage(pm,
- SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId),
- userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
+ String voiceSearchPackage = getDefaultSystemHandlerActivityPackage(pm,
+ SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId);
+ grantPermissionsToSystemPackage(pm, voiceSearchPackage,
+ userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS,
+ COARSE_BACKGROUND_LOCATION_PERMISSIONS);
+ revokeRuntimePermissions(pm, voiceSearchPackage,
+ FINE_LOCATION_PERMISSIONS, false, userId);
}
// Print Spooler
@@ -1269,6 +1280,7 @@ final class DefaultPermissionGrantPolicy {
*/
private boolean isFixedOrUserSet(int flags) {
return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_ONE_TIME
| PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED
| PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) != 0;
@@ -1628,6 +1640,14 @@ final class DefaultPermissionGrantPolicy {
continue;
}
+ // If the trunkstable feature flag is disabled for this
+ // exception, skip the tag.
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(
+ /* pkg= */ null, parser, /* allowNoNamespace= */ true)) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
final boolean fixed =
parser.getAttributeBoolean(null, ATTR_FIXED, false);
final boolean whitelisted =
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5fc3e332b95c..05bc69a9f1f0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,7 +1015,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
permission, attributionSource, message, forDataDelivery, startDataDelivery,
fromDatasource, attributedOp);
// Finish any started op if some step in the attribution chain failed.
- if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+ if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+ && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
if (attributedOp == AppOpsManager.OP_NONE) {
finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
attributionSource.asState(), fromDatasource);
@@ -1244,6 +1245,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
AttributionSource current = attributionSource;
AttributionSource next = null;
+ AttributionSource prev = null;
// We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
// every attributionSource in the chain is registered with the system.
final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1310,6 +1312,22 @@ public class PermissionManagerService extends IPermissionManager.Stub {
selfAccess, singleReceiverFromDatasource, attributedOp,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
+ if (startDataDelivery && opMode != AppOpsManager.MODE_ALLOWED) {
+ // Current failed the perm check, so if we are part-way through an attr chain,
+ // we need to clean up the already started proxy op higher up the chain. Note,
+ // proxy ops are verified two by two, which means we have to clear the 2nd next
+ // from the previous iteration (since it is actually curr.next which failed
+ // to pass the perm check).
+ if (prev != null) {
+ final var cutAttrSourceState = prev.asState();
+ if (cutAttrSourceState.next.length > 0) {
+ cutAttrSourceState.next[0].next = new AttributionSourceState[0];
+ }
+ finishDataDelivery(context, attributedOp,
+ cutAttrSourceState, fromDatasource);
+ }
+ }
+
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
@@ -1335,6 +1353,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return PermissionChecker.PERMISSION_GRANTED;
}
+ // an attribution we have already possibly started an op for
+ prev = current;
current = next;
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index bbc17c83cfac..33fc066a62ee 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -198,6 +199,21 @@ public interface PackageState {
int getCategoryOverride();
/**
+ * Returns true if ELF files will be loaded in Page size compatibility mode
+ *
+ * @hide
+ */
+ boolean isPageSizeAppCompatEnabled();
+
+ /**
+ * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+ * alignment.
+ *
+ * @hide
+ */
+ String getPageSizeCompatWarningMessage(Context context);
+
+ /**
* The install time CPU override, if any. This value is written at install time
* and doesn't change during the life of an install. If non-null,
* {@link #getPrimaryCpuAbiLegacy()} will also contain the same value.
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 253eb4006122..a46c4a695d60 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -257,6 +257,16 @@ public class PackageStateMutator {
@NonNull
@Override
+ public PackageStateWrite setPageSizeAppCompatFlags(
+ @ApplicationInfo.PageSizeAppCompatFlags int mode) {
+ if (mState != null) {
+ mState.setPageSizeAppCompatFlags(mode);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
if (mState != null) {
mState.setUpdateAvailable(updateAvailable);
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 55d96f3aee08..f8f8695b2832 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -46,6 +46,10 @@ public interface PackageStateWrite {
@NonNull
PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category);
+ /** set 16Kb App compat mode. @see ApplicationInfo.PageSizeAppCompatFlags */
+ @NonNull
+ PackageStateWrite setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int mode);
+
@NonNull
PackageStateWrite setUpdateAvailable(boolean updateAvailable);
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 453c6ef41437..e75f852eb437 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -137,6 +137,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
"com.android.server.policy.PROPERTY_FEATURE_REAR_DISPLAY";
private static final String PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT =
"com.android.server.policy.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT";
+ private static final String PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT =
+ "com.android.server.policy.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT";
@@ -281,6 +283,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
case PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT:
systemProperties.add(DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT);
break;
+ case PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT:
+ systemProperties.add(DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT);
+ break;
default:
Slog.w(TAG, "Parsed unknown flag with name: " + propertyString);
break;
diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java
index 9dfaca8d163f..1592ef306cb3 100644
--- a/services/core/java/com/android/server/policy/KeyCombinationManager.java
+++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java
@@ -65,21 +65,21 @@ public class KeyCombinationManager {
* };
* </pre>
*/
- abstract static class TwoKeysCombinationRule {
+ public abstract static class TwoKeysCombinationRule {
private int mKeyCode1;
private int mKeyCode2;
- TwoKeysCombinationRule(int keyCode1, int keyCode2) {
+ public TwoKeysCombinationRule(int keyCode1, int keyCode2) {
mKeyCode1 = keyCode1;
mKeyCode2 = keyCode2;
}
- boolean preCondition() {
+ public boolean preCondition() {
return true;
}
boolean shouldInterceptKey(int keyCode) {
- return preCondition() && (keyCode == mKeyCode1 || keyCode == mKeyCode2);
+ return (keyCode == mKeyCode1 || keyCode == mKeyCode2) && preCondition();
}
boolean shouldInterceptKeys(SparseLongArray downTimes) {
@@ -94,12 +94,12 @@ public class KeyCombinationManager {
}
// The excessive delay before it dispatching to client.
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
return COMBINE_KEY_DELAY_MILLIS;
}
- abstract void execute();
- abstract void cancel();
+ public abstract void execute();
+ public abstract void cancel();
@Override
public String toString() {
@@ -128,18 +128,18 @@ public class KeyCombinationManager {
}
}
- KeyCombinationManager(Handler handler) {
+ public KeyCombinationManager(Handler handler) {
mHandler = handler;
}
- void addRule(TwoKeysCombinationRule rule) {
+ public void addRule(TwoKeysCombinationRule rule) {
if (mRules.contains(rule)) {
throw new IllegalArgumentException("Rule : " + rule + " already exists.");
}
mRules.add(rule);
}
- void removeRule(TwoKeysCombinationRule rule) {
+ public void removeRule(TwoKeysCombinationRule rule) {
mRules.remove(rule);
}
@@ -148,7 +148,7 @@ public class KeyCombinationManager {
* to a window.
* Return true if any active rule could be triggered by the key event, otherwise false.
*/
- boolean interceptKey(KeyEvent event, boolean interactive) {
+ public boolean interceptKey(KeyEvent event, boolean interactive) {
synchronized (mLock) {
return interceptKeyLocked(event, interactive);
}
@@ -226,7 +226,7 @@ public class KeyCombinationManager {
/**
* Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window.
*/
- long getKeyInterceptTimeout(int keyCode) {
+ public long getKeyInterceptTimeout(int keyCode) {
synchronized (mLock) {
if (mDownTimes.get(keyCode) == 0) {
return 0;
@@ -246,7 +246,7 @@ public class KeyCombinationManager {
/**
* True if the key event had been handled.
*/
- boolean isKeyConsumed(KeyEvent event) {
+ public boolean isKeyConsumed(KeyEvent event) {
synchronized (mLock) {
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
return false;
@@ -258,7 +258,7 @@ public class KeyCombinationManager {
/**
* True if power key is the candidate.
*/
- boolean isPowerKeyIntercepted() {
+ public boolean isPowerKeyIntercepted() {
synchronized (mLock) {
if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) {
// return false if only if power key pressed.
@@ -294,7 +294,7 @@ public class KeyCombinationManager {
return false;
}
- void dump(String prefix, PrintWriter pw) {
+ public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "KeyCombination rules:");
forAllRules(mRules, (rule)-> {
pw.println(prefix + " " + rule);
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 027e69cbc09b..c9f66eb5ccb2 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -29,8 +29,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
import android.hardware.input.KeyGestureEvent;
import android.os.Handler;
import android.os.RemoteException;
@@ -48,6 +51,7 @@ import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
import com.android.internal.util.XmlUtils;
@@ -62,7 +66,9 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -80,6 +86,7 @@ public class ModifierShortcutManager {
private static final String ATTRIBUTE_PACKAGE = "package";
private static final String ATTRIBUTE_CLASS = "class";
private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_KEYCODE = "keycode";
private static final String ATTRIBUTE_CATEGORY = "category";
private static final String ATTRIBUTE_SHIFT = "shift";
private static final String ATTRIBUTE_ROLE = "role";
@@ -131,7 +138,10 @@ public class ModifierShortcutManager {
private boolean mConsumeSearchKeyUp = true;
private UserHandle mCurrentUser;
private final Map<Pair<Character, Boolean>, Bookmark> mBookmarks = new HashMap<>();
+ @GuardedBy("mAppIntentCache")
+ private final Map<AppLaunchData, Intent> mAppIntentCache = new HashMap<>();
+ @SuppressLint("MissingPermission")
ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
mContext = context;
mHandler = handler;
@@ -146,9 +156,23 @@ public class ModifierShortcutManager {
} else {
mRoleIntents.remove(roleName);
}
+ synchronized (mAppIntentCache) {
+ mAppIntentCache.entrySet().removeIf(
+ entry -> {
+ if (entry.getKey() instanceof AppLaunchData.RoleData) {
+ return Objects.equals(
+ ((AppLaunchData.RoleData) entry.getKey()).getRole(),
+ roleName);
+ }
+ return false;
+ });
+ }
}, UserHandle.ALL);
mCurrentUser = currentUser;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ }
+
+ void onSystemReady() {
loadShortcuts();
}
@@ -159,6 +183,10 @@ public class ModifierShortcutManager {
// so clear the cache.
clearRoleIntents();
clearComponentIntents();
+
+ synchronized (mAppIntentCache) {
+ mAppIntentCache.clear();
+ }
}
void clearRoleIntents() {
@@ -313,6 +341,7 @@ public class ModifierShortcutManager {
try {
XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
while (true) {
XmlUtils.nextElement(parser);
@@ -331,15 +360,36 @@ public class ModifierShortcutManager {
String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+ final int keycode;
+ final int modifierState;
+ TypedArray a = mContext.getResources().obtainAttributes(parser,
+ R.styleable.Bookmark);
+ try {
+ keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+ modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+ } finally {
+ a.recycle();
+ }
+ if (TextUtils.isEmpty(shortcutName) && keycode != KeyEvent.KEYCODE_UNKNOWN) {
+ // Try to find shortcutChar using keycode
+ shortcutName = String.valueOf(virtualKcm.getDisplayLabel(keycode)).toLowerCase(
+ Locale.ROOT);
+ }
if (TextUtils.isEmpty(shortcutName)) {
Log.w(TAG, "Shortcut required for bookmark with category=" + categoryName
+ " packageName=" + packageName + " className=" + className
- + " role=" + roleName + "shiftName=" + shiftName);
+ + " role=" + roleName + " shiftName=" + shiftName + " keycode= "
+ + keycode + " modifierState= " + modifierState);
continue;
}
- final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+ final boolean isShiftShortcut;
+ if (!TextUtils.isEmpty(shiftName)) {
+ isShiftShortcut = shiftName.equals("true");
+ } else {
+ isShiftShortcut = (modifierState & KeyEvent.META_SHIFT_ON) != 0;
+ }
if (modifierShortcutManagerRefactor()) {
final char shortcutChar = shortcutName.charAt(0);
@@ -354,7 +404,7 @@ public class ModifierShortcutManager {
bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
}
if (bookmark != null) {
- Log.d(TAG, "adding shortcut " + bookmark + "shift="
+ Log.d(TAG, "adding shortcut " + bookmark + " shift="
+ isShiftShortcut + " char=" + shortcutChar);
mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
}
@@ -667,9 +717,11 @@ public class ModifierShortcutManager {
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
List<KeyboardShortcutInfo> shortcuts = new ArrayList();
if (modifierShortcutManagerRefactor()) {
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
for (Bookmark b : mBookmarks.values()) {
KeyboardShortcutInfo info = shortcutInfoFromIntent(
- b.getShortcutChar(), b.getIntent(mContext), b.isShift());
+ b.getShortcutChar(), b.getIntent(context), b.isShift());
if (info != null) {
shortcuts.add(info);
}
@@ -747,6 +799,70 @@ public class ModifierShortcutManager {
}
/**
+ * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+ * shortcuts based on provided list of shortcut data.
+ */
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId,
+ List<InputGestureData> shortcutData) {
+ List<KeyboardShortcutInfo> shortcuts = new ArrayList<>();
+ KeyCharacterMap kcm = KeyCharacterMap.load(deviceId);
+ for (InputGestureData data : shortcutData) {
+ if (data.getTrigger() instanceof InputGestureData.KeyTrigger trigger) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ kcm.getDisplayLabel(trigger.getKeycode()),
+ getIntentFromAppLaunchData(data.getAction().appLaunchData()),
+ (trigger.getModifierState() & KeyEvent.META_SHIFT_ON) != 0);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+ }
+ return new KeyboardShortcutGroup(
+ mContext.getString(R.string.keyboard_shortcut_group_applications),
+ shortcuts);
+ }
+
+ private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) {
+ Context context = mContext.createContextAsUser(mCurrentUser, 0);
+ synchronized (mAppIntentCache) {
+ Intent intent = mAppIntentCache.get(data);
+ if (intent != null) {
+ return intent;
+ }
+ if (data instanceof AppLaunchData.CategoryData) {
+ intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+ ((AppLaunchData.CategoryData) data).getCategory());
+ } else if (data instanceof AppLaunchData.RoleData) {
+ intent = getRoleLaunchIntent(context, ((AppLaunchData.RoleData) data).getRole());
+ } else if (data instanceof AppLaunchData.ComponentData) {
+ AppLaunchData.ComponentData componentData = (AppLaunchData.ComponentData) data;
+ intent = resolveComponentNameIntent(context, componentData.getPackageName(),
+ componentData.getClassName());
+ }
+ if (intent != null) {
+ mAppIntentCache.put(data, intent);
+ }
+ return intent;
+ }
+ }
+
+ boolean launchApplication(@NonNull AppLaunchData data) {
+ Intent intent = getIntentFromAppLaunchData(data);
+ if (intent == null) {
+ return false;
+ }
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivityAsUser(intent, mCurrentUser);
+ return true;
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Not launching app because "
+ + "the activity to which it refers to was not found: " + data);
+ }
+ return false;
+ }
+
+ /**
* Given an intent to launch an application and the character and shift state that should
* trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and
* icon for the target application.
@@ -867,7 +983,7 @@ public class ModifierShortcutManager {
// TODO(b/280423320): Add new field package name associated in the
// KeyboardShortcutEvent atom and log it accordingly.
- return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+ return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION;
}
@KeyGestureEvent.KeyGestureType
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e0c4ebeb9f89..f1a481155458 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -83,7 +83,10 @@ import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
-import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
@@ -147,7 +150,9 @@ 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.AppLaunchData;
import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
import android.hardware.input.KeyGestureEvent;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -179,6 +184,7 @@ import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
import android.provider.Settings.Secure;
+import android.service.SensorPrivacyToggleSourceProto;
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -198,7 +204,6 @@ import android.view.HapticFeedbackConstants;
import android.view.IDisplayFoldListener;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
-import android.view.KeyCharacterMap.FallbackAction;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.MotionEvent;
@@ -373,6 +378,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
//The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS
static final int DOUBLE_PRESS_PRIMARY_NOTHING = 0;
static final int DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP = 1;
+ static final int DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP = 2;
// Must match: config_triplePressOnStemPrimaryBehavior in config.xml
// The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS
@@ -698,10 +704,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Maps global key codes to the components that will handle them.
private GlobalKeyManager mGlobalKeyManager;
- // Fallback actions by key code.
- private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
- new SparseArray<KeyCharacterMap.FallbackAction>();
-
private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
= new LogDecelerateInterpolator(100, 0);
private final DeferredKeyActionExecutor mDeferredKeyActionExecutor =
@@ -1059,7 +1061,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return handled;
}
- private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
+ private void interceptPowerKeyDown(KeyEvent event, boolean interactive,
+ boolean isKeyGestureTriggered) {
// Hold a wake lock until the power key is released.
if (!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
@@ -1092,7 +1095,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
mPowerKeyHandled = mPowerKeyHandled || hungUp
- || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
+ || handledByPowerManager || isKeyGestureTriggered
+ || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
if (!interactive) {
wakeUpFromWakeKey(event);
@@ -1601,6 +1605,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
performStemPrimaryDoublePressSwitchToRecentTask();
}
break;
+ case DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP:
+ final int stemPrimaryKeyDeviceId = INVALID_INPUT_DEVICE_ID;
+ handleKeyGestureInKeyGestureController(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
+ stemPrimaryKeyDeviceId, KEYCODE_STEM_PRIMARY, /* metaState= */ 0);
+ break;
}
}
@@ -1610,7 +1620,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case TRIPLE_PRESS_PRIMARY_NOTHING:
break;
case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
- mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
+ mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+ TalkbackShortcutController.ShortcutSource.GESTURE);
if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
performHapticFeedback(HapticFeedbackConstants.CONFIRM,
"Stem primary - Triple Press - Toggle Accessibility");
@@ -2485,6 +2496,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private void initKeyCombinationRules() {
mKeyCombinationManager = new KeyCombinationManager(mHandler);
+ if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
+ return;
+ }
final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableScreenshotChord);
@@ -2492,13 +2506,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
@Override
- void execute() {
+ public void execute() {
mPowerKeyHandled = true;
interceptScreenshotChord(
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
}
@Override
- void cancel() {
+ public void cancel() {
cancelPendingScreenshotChordAction();
}
});
@@ -2507,13 +2521,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_POWER, KEYCODE_STEM_PRIMARY) {
@Override
- void execute() {
+ public void execute() {
mPowerKeyHandled = true;
interceptScreenshotChord(SCREENSHOT_KEY_CHORD,
getScreenshotChordLongPressDelay());
}
@Override
- void cancel() {
+ public void cancel() {
cancelPendingScreenshotChordAction();
}
});
@@ -2523,16 +2537,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP) {
@Override
- boolean preCondition() {
+ public boolean preCondition() {
return mAccessibilityShortcutController
.isAccessibilityShortcutAvailable(isKeyguardLocked());
}
@Override
- void execute() {
+ public void execute() {
interceptAccessibilityShortcutChord();
}
@Override
- void cancel() {
+ public void cancel() {
cancelPendingAccessibilityShortcutAction();
}
});
@@ -2543,7 +2557,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER) {
@Override
- boolean preCondition() {
+ public boolean preCondition() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
return mRingerToggleChord != VOLUME_HUSH_OFF;
@@ -2552,7 +2566,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
@Override
- void execute() {
+ public void execute() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
// no haptic feedback here since
@@ -2571,7 +2585,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
@Override
- void cancel() {
+ public void cancel() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
cancelPendingRingerToggleChordAction();
@@ -2587,16 +2601,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) {
@Override
- void execute() {
+ public void execute() {
mBackKeyHandled = true;
interceptAccessibilityGestureTv();
}
@Override
- void cancel() {
+ public void cancel() {
cancelAccessibilityGestureTv();
}
@Override
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
// Use a timeout of 0 to prevent additional latency in processing of
// this key. This will potentially cause some unwanted UI actions if the
// user does end up triggering the key combination later, but in most
@@ -2610,16 +2624,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) {
@Override
- void execute() {
+ public void execute() {
mBackKeyHandled = true;
interceptBugreportGestureTv();
}
@Override
- void cancel() {
+ public void cancel() {
cancelBugreportGestureTv();
}
@Override
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
return 0;
}
});
@@ -3223,8 +3237,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return ADD_OKAY;
}
- // Allow virtual device owners to add overlays on the displays they own.
+ // Allow virtual device owners to add overlays on the trusted displays they own.
if (mWindowManagerFuncs.isCallerVirtualDeviceOwner(displayId, callingUid)
+ && mWindowManagerFuncs.isDisplayTrusted(displayId)
&& mContext.checkCallingOrSelfPermission(CREATE_VIRTUAL_DEVICE)
== PERMISSION_GRANTED) {
return ADD_OKAY;
@@ -3401,6 +3416,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ if (useKeyGestureEventHandler()) {
+ return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId,
+ mInputManager.getAppLaunchBookmarks());
+ }
return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
}
@@ -3423,15 +3442,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
+ keyguardOn() + " canceled=" + event.isCanceled());
}
- if (mKeyCombinationManager.isKeyConsumed(event)) {
- return keyConsumed;
- }
+ if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
+ if (mKeyCombinationManager.isKeyConsumed(event)) {
+ return keyConsumed;
+ }
- if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
- final long now = SystemClock.uptimeMillis();
- final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
- if (now < interceptTimeout) {
- return interceptTimeout - now;
+ if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ final long now = SystemClock.uptimeMillis();
+ final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(
+ keyCode);
+ if (now < interceptTimeout) {
+ return interceptTimeout - now;
+ }
}
}
@@ -3473,8 +3495,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// NOTE: Please try not to add new Shortcut combinations here and instead use KeyGestureEvents.
// Add shortcut trigger logic in {@code KeyGestureController} and add handling logic in
// {@link handleKeyGesture()}
- @SuppressLint("MissingPermission")
private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+ if (useKeyGestureEventHandler()) {
+ return interceptSystemKeysAndShortcutsNew(focusedToken, event);
+ } else {
+ return interceptSystemKeysAndShortcutsOld(focusedToken, event);
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private boolean interceptSystemKeysAndShortcutsOld(IBinder focusedToken, KeyEvent event) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
@@ -3583,12 +3613,87 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
+ case KeyEvent.KEYCODE_T:
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
+ mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+ TalkbackShortcutController.ShortcutSource.KEYBOARD);
+ notifyKeyGestureCompleted(event,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK);
+ return true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_3:
+ if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (firstDown && event.isMetaPressed()
+ && event.isAltPressed()) {
+ final boolean bounceKeysEnabled =
+ InputSettings.isAccessibilityBounceKeysEnabled(
+ mContext);
+ InputSettings.setAccessibilityBounceKeysThreshold(mContext,
+ bounceKeysEnabled ? 0
+ : InputSettings.DEFAULT_BOUNCE_KEYS_THRESHOLD_MILLIS);
+ notifyKeyGestureCompleted(event,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS);
+ return true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_4:
+ if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
+ final boolean mouseKeysEnabled =
+ InputSettings.isAccessibilityMouseKeysEnabled(
+ mContext);
+ InputSettings.setAccessibilityMouseKeysEnabled(mContext,
+ !mouseKeysEnabled);
+ notifyKeyGestureCompleted(event,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS);
+ return true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_5:
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
+ final boolean stickyKeysEnabled =
+ InputSettings.isAccessibilityStickyKeysEnabled(
+ mContext);
+ InputSettings.setAccessibilityStickyKeysEnabled(mContext,
+ !stickyKeysEnabled);
+ notifyKeyGestureCompleted(event,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS);
+ return true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_6:
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
+ final boolean slowKeysEnabled =
+ InputSettings.isAccessibilitySlowKeysEnabled(mContext);
+ InputSettings.setAccessibilitySlowKeysThreshold(mContext,
+ slowKeysEnabled ? 0
+ : InputSettings.DEFAULT_SLOW_KEYS_THRESHOLD_MILLIS);
+ notifyKeyGestureCompleted(event,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS);
+ return true;
+ }
+ }
+ break;
case KeyEvent.KEYCODE_DEL:
if (newBugreportKeyboardShortcut()) {
if (mEnableBugReportKeyboardShortcut && firstDown
&& event.isMetaPressed() && event.isCtrlPressed()) {
try {
- mActivityManagerService.requestInteractiveBugReport();
+ if (!mActivityManagerService.launchBugReportHandlerApp()) {
+ mActivityManagerService.requestInteractiveBugReport();
+ }
} catch (RemoteException e) {
Slog.d(TAG, "Error taking bugreport", e);
}
@@ -3888,10 +3993,60 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
case KeyEvent.KEYCODE_SCREENSHOT:
- if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) {
+ if (firstDown) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
}
return true;
+ case KeyEvent.KEYCODE_DO_NOT_DISTURB:
+ case KeyEvent.KEYCODE_LOCK:
+ case KeyEvent.KEYCODE_FULLSCREEN:
+ return true;
+ }
+ if (isValidGlobalKey(keyCode)
+ && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
+ return true;
+ }
+
+ // Reserve all the META modifier combos for system behavior
+ return (metaState & KeyEvent.META_META_ON) != 0;
+ }
+
+ private boolean interceptSystemKeysAndShortcutsNew(IBinder focusedToken, KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_HOME:
+ return handleHomeShortcuts(focusedToken, event);
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ if (mUseTvRouting || mHandleVolumeKeysInWM) {
+ // On TVs or when the configuration is enabled, volume keys never
+ // go to the foreground app.
+ dispatchDirectAudioEvent(event);
+ return true;
+ }
+
+ // If the device is in VR mode and keys are "internal" (e.g. on the side of the
+ // device), then drop the volume keys and don't forward it to the
+ // application/dispatch the audio event.
+ if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
+ final InputDevice d = event.getDevice();
+ if (d != null && !d.isExternal()) {
+ return true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_STEM_PRIMARY:
+ if (prepareToSendSystemKeyToApplication(focusedToken, event)) {
+ // Send to app.
+ return false;
+ } else {
+ // Intercepted.
+ sendSystemKeyToStatusBarAsync(event);
+ return true;
+ }
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -3911,7 +4066,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
@Nullable IBinder focusedToken) {
- return PhoneWindowManager.this.handleKeyGestureEvent(event, focusedToken);
+ boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
+ focusedToken);
+ if (handled && Arrays.stream(event.getKeycodes()).anyMatch(
+ (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
+ mPowerKeyHandled = true;
+ }
+ return handled;
}
@Override
@@ -3920,6 +4081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS:
case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
case KeyGestureEvent.KEY_GESTURE_TYPE_HOME:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN:
@@ -3941,7 +4103,35 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
+ return mDefaultDisplayPolicy.isAwake();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
+ return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController
+ .isAccessibilityShortcutAvailable(isKeyguardLocked());
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
+ return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController
+ .isAccessibilityShortcutAvailable(false);
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
+ return enableTalkbackAndMagnifierKeyGestures();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
+ return InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
+ && keyboardA11yShortcutControl();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS:
+ return InputSettings.isAccessibilityBounceKeysFeatureEnabled()
+ && keyboardA11yShortcutControl();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS:
+ return InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()
+ && keyboardA11yShortcutControl();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS:
+ return InputSettings.isAccessibilityStickyKeysFeatureEnabled()
+ && keyboardA11yShortcutControl();
default:
return false;
}
@@ -3975,6 +4165,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
if (complete) {
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
deviceId, SystemClock.uptimeMillis(),
@@ -4010,7 +4201,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
if (complete && mEnableBugReportKeyboardShortcut) {
try {
- mActivityManagerService.requestInteractiveBugReport();
+ if (!mActivityManagerService.launchBugReportHandlerApp()) {
+ mActivityManagerService.requestInteractiveBugReport();
+ }
} catch (RemoteException e) {
Slog.d(TAG, "Error taking bugreport", e);
}
@@ -4099,6 +4292,134 @@ public class PhoneWindowManager implements WindowManagerPolicy {
sendSwitchKeyboardLayout(displayId, focusedToken, direction);
}
return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
+ if (start) {
+ // Screenshot chord is pressed: Wait for long press delay before taking
+ // screenshot
+ interceptScreenshotChord(SCREENSHOT_KEY_CHORD,
+ getScreenshotChordLongPressDelay());
+ } else {
+ cancelPendingScreenshotChordAction();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
+ if (start) {
+ interceptAccessibilityShortcutChord();
+ } else {
+ cancelPendingAccessibilityShortcutAction();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
+ if (start) {
+ interceptRingerToggleChord();
+ } else {
+ cancelPendingRingerToggleChordAction();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
+ if (start) {
+ performHapticFeedback(
+ HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
+ "KEY_GESTURE_TYPE_GLOBAL_ACTIONS - Global Actions");
+ showGlobalActions();
+ } else {
+ cancelGlobalActionsAction();
+ }
+ return true;
+ // TODO (b/358569822): Consolidate TV and non-TV gestures into same KeyGestureEvent
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
+ if (start) {
+ interceptAccessibilityGestureTv();
+ } else {
+ cancelAccessibilityGestureTv();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
+ if (start) {
+ interceptBugreportGestureTv();
+ } else {
+ cancelBugreportGestureTv();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
+ if (complete && mAccessibilityShortcutController.isAccessibilityShortcutAvailable(
+ isKeyguardLocked())) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT));
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
+ if (complete) {
+ mContext.closeSystemDialogs();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ if (complete) {
+ mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+ TalkbackShortcutController.ShortcutSource.KEYBOARD);
+ }
+ return true;
+ }
+ break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
+ AppLaunchData data = event.getAppLaunchData();
+ if (complete && isUserSetupComplete() && !keyguardOn
+ && data != null && mModifierShortcutManager.launchApplication(data)) {
+ dismissKeyboardShortcutsMenu();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS:
+ if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (complete) {
+ final boolean bounceKeysEnabled =
+ InputSettings.isAccessibilityBounceKeysEnabled(
+ mContext);
+ InputSettings.setAccessibilityBounceKeysThreshold(mContext,
+ bounceKeysEnabled ? 0
+ : InputSettings.DEFAULT_BOUNCE_KEYS_THRESHOLD_MILLIS);
+ }
+ return true;
+ }
+ break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS:
+ if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (complete) {
+ final boolean mouseKeysEnabled =
+ InputSettings.isAccessibilityMouseKeysEnabled(
+ mContext);
+ InputSettings.setAccessibilityMouseKeysEnabled(mContext,
+ !mouseKeysEnabled);
+ }
+ return true;
+ }
+ break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS:
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (complete) {
+ final boolean stickyKeysEnabled =
+ InputSettings.isAccessibilityStickyKeysEnabled(mContext);
+ InputSettings.setAccessibilityStickyKeysEnabled(mContext,
+ !stickyKeysEnabled);
+ }
+ return true;
+ }
+ break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
+ && keyboardA11yShortcutControl()) {
+ if (complete) {
+ final boolean slowKeysEnabled =
+ InputSettings.isAccessibilitySlowKeysEnabled(mContext);
+ InputSettings.setAccessibilitySlowKeysThreshold(mContext,
+ slowKeysEnabled ? 0
+ : InputSettings.DEFAULT_SLOW_KEYS_THRESHOLD_MILLIS);
+ }
+ return true;
+ }
+ break;
}
return false;
}
@@ -4107,9 +4428,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
float minLinearBrightness = mPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+ screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+ screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
@@ -4222,8 +4543,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
SensorPrivacyManager.Sensors.MICROPHONE);
- mSensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sensors.MICROPHONE,
- !isEnabled);
+ mSensorPrivacyManager.setSensorPrivacy(SensorPrivacyToggleSourceProto.OTHER,
+ SensorPrivacyManager.Sensors.MICROPHONE, !isEnabled, mCurrentUserId);
int toastTextResId;
if (isEnabled) {
@@ -4270,7 +4591,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mHandler.removeMessages(MSG_ACCESSIBILITY_TV);
}
- private void requestBugreportForTv() {
+ @VisibleForTesting
+ void requestBugreportForTv() {
try {
if (!ActivityManager.getService().launchBugReportHandlerApp()) {
ActivityManager.getService().requestInteractiveBugReport();
@@ -4283,7 +4605,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// TODO(b/117479243): handle it in InputPolicy
/** {@inheritDoc} */
@Override
- public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
+ public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
// Note: This method is only called if the initial down was unhandled.
if (DEBUG_INPUT) {
final KeyInterceptionInfo info =
@@ -4296,75 +4618,26 @@ public class PhoneWindowManager implements WindowManagerPolicy {
+ ", keyCode=" + event.getKeyCode()
+ ", scanCode=" + event.getScanCode()
+ ", metaState=" + event.getMetaState()
- + ", repeatCount=" + event.getRepeatCount()
- + ", policyFlags=" + policyFlags);
- }
-
- if (interceptUnhandledKey(event, focusedToken)) {
- return null;
- }
-
- KeyEvent fallbackEvent = null;
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- final KeyCharacterMap kcm = event.getKeyCharacterMap();
- final int keyCode = event.getKeyCode();
- final int metaState = event.getMetaState();
- final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0;
-
- // Check for fallback actions specified by the key character map.
- final FallbackAction fallbackAction;
- if (initialDown) {
- fallbackAction = kcm.getFallbackAction(keyCode, metaState);
- } else {
- fallbackAction = mFallbackActions.get(keyCode);
- }
-
- if (fallbackAction != null) {
- if (DEBUG_INPUT) {
- Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
- + " metaState=" + Integer.toHexString(fallbackAction.metaState));
- }
-
- final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
- fallbackEvent = KeyEvent.obtain(
- event.getDownTime(), event.getEventTime(),
- event.getAction(), fallbackAction.keyCode,
- event.getRepeatCount(), fallbackAction.metaState,
- event.getDeviceId(), event.getScanCode(),
- flags, event.getSource(), event.getDisplayId(), null);
-
- if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
- fallbackEvent.recycle();
- fallbackEvent = null;
- }
-
- if (initialDown) {
- mFallbackActions.put(keyCode, fallbackAction);
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- mFallbackActions.remove(keyCode);
- fallbackAction.recycle();
- }
- }
- }
-
- if (DEBUG_INPUT) {
- if (fallbackEvent == null) {
- Slog.d(TAG, "No fallback.");
- } else {
- Slog.d(TAG, "Performing fallback: " + fallbackEvent);
- }
+ + ", repeatCount=" + event.getRepeatCount());
}
- return fallbackEvent;
- }
- private boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int metaState = event.getModifiers();
- switch(keyCode) {
+ // TODO(b/358569822): Shift to KeyGestureEvent based handling
+ if (keyCode == KeyEvent.KEYCODE_STEM_PRIMARY) {
+ handleUnhandledSystemKey(event);
+ sendSystemKeyToStatusBarAsync(event);
+ return true;
+ }
+
+ if (useKeyGestureEventHandler()) {
+ return false;
+ }
+
+ switch (keyCode) {
case KeyEvent.KEYCODE_SPACE:
if (down && repeatCount == 0) {
// Handle keyboard layout switching. (CTRL + SPACE)
@@ -4401,10 +4674,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
- case KeyEvent.KEYCODE_STEM_PRIMARY:
- handleUnhandledSystemKey(event);
- sendSystemKeyToStatusBarAsync(event);
- return true;
}
return false;
@@ -4443,19 +4712,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
targetWindowToken);
}
- private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
- int policyFlags) {
- int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
- if ((actions & ACTION_PASS_TO_USER) != 0) {
- long delayMillis = interceptKeyBeforeDispatching(
- focusedToken, fallbackEvent, policyFlags);
- if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken)) {
- return true;
- }
- }
- return false;
- }
-
@Override
public void setTopFocusedDisplay(int displayId) {
mTopFocusedDisplayId = displayId;
@@ -4502,6 +4758,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
synchronized (mLock) {
+ if (useKeyGestureEventHandler()) {
+ mInputManagerInternal.registerShortcutKey(shortcutCode, shortcutService);
+ return;
+ }
mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
}
}
@@ -4915,6 +5175,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
|| event.isWakeKey();
+ boolean isKeyGestureTriggered = (policyFlags & FLAG_KEY_GESTURE_TRIGGERED) != 0;
// There are key events that perform the operation as the current user,
// and these should be ignored for visible background users.
@@ -5041,8 +5302,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
final boolean isDefaultDisplayAwake = mDefaultDisplayPolicy.isAwake();
final boolean interactiveAndAwake = interactive && isDefaultDisplayAwake;
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- handleKeyGesture(event, interactiveAndAwake, isDefaultDisplayOn);
+ if (isKeyGestureTriggered) {
+ // If key gesture is triggered outside policy, reset gesture handlers here
+ mSingleKeyGestureDetector.reset();
+ } else {
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ handleKeyGesture(event, interactiveAndAwake, isDefaultDisplayOn);
+ }
}
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
@@ -5205,7 +5471,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
- interceptPowerKeyDown(event, interactiveAndAwake);
+ interceptPowerKeyDown(event, interactiveAndAwake, isKeyGestureTriggered);
} else {
interceptPowerKeyUp(event, canceled);
}
@@ -5406,9 +5672,23 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_MACRO_4:
result &= ~ACTION_PASS_TO_USER;
break;
- case KeyEvent.KEYCODE_EMOJI_PICKER:
- if (!emojiAndScreenshotKeycodesAvailable()) {
- // Don't allow EMOJI_PICKER key to be dispatched until flag is released.
+ case KeyEvent.KEYCODE_DICTATE:
+ case KeyEvent.KEYCODE_NEW:
+ case KeyEvent.KEYCODE_CLOSE:
+ case KeyEvent.KEYCODE_PRINT:
+ case KeyEvent.KEYCODE_F13:
+ case KeyEvent.KEYCODE_F14:
+ case KeyEvent.KEYCODE_F15:
+ case KeyEvent.KEYCODE_F16:
+ case KeyEvent.KEYCODE_F17:
+ case KeyEvent.KEYCODE_F18:
+ case KeyEvent.KEYCODE_F19:
+ case KeyEvent.KEYCODE_F20:
+ case KeyEvent.KEYCODE_F21:
+ case KeyEvent.KEYCODE_F22:
+ case KeyEvent.KEYCODE_F23:
+ case KeyEvent.KEYCODE_F24:
+ if (!enableNew25q2Keycodes()) {
result &= ~ACTION_PASS_TO_USER;
}
break;
@@ -5442,7 +5722,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void handleKeyGesture(KeyEvent event, boolean interactive, boolean defaultDisplayOn) {
- if (mKeyCombinationManager.interceptKey(event, interactive)) {
+ if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
+ && mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
@@ -5581,8 +5862,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5596,8 +5877,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5945,7 +6226,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(DEFAULT_DISPLAY, eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
@@ -6332,6 +6613,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// In normal flow, systemReady is called before other system services are ready.
// So it is better not to bind keyguard here.
mKeyguardDelegate.onSystemReady();
+ mModifierShortcutManager.onSystemReady();
mVrManagerInternal = LocalServices.getService(VrManagerInternal.class);
if (mVrManagerInternal != null) {
@@ -6827,6 +7109,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (modifierShortcutManagerMultiuser()) {
mModifierShortcutManager.setCurrentUser(UserHandle.of(newUserId));
}
+ if (!inputManagerLifecycleSupport()) {
+ mInputManagerInternal.setCurrentUser(newUserId);
+ }
}
@Override
@@ -7188,6 +7473,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return "DOUBLE_PRESS_PRIMARY_NOTHING";
case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
return "DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP";
+ case DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP:
+ return "DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP";
default:
return Integer.toString(behavior);
}
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index e544ae64521c..9e16a7d5e83a 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -44,6 +44,11 @@ class TalkbackShortcutController {
private final Context mContext;
private final PackageManager mPackageManager;
+ public enum ShortcutSource {
+ GESTURE,
+ KEYBOARD,
+ }
+
TalkbackShortcutController(Context context) {
mContext = context;
mPackageManager = mContext.getPackageManager();
@@ -55,7 +60,7 @@ class TalkbackShortcutController {
* @return talkback state after toggle. {@code true} if talkback is enabled, {@code false} if
* talkback is disabled
*/
- boolean toggleTalkback(int userId) {
+ boolean toggleTalkback(int userId, ShortcutSource source) {
final Set<ComponentName> enabledServices =
AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
ComponentName componentName = getTalkbackComponent();
@@ -65,13 +70,13 @@ class TalkbackShortcutController {
boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
- if (isTalkBackShortcutGestureEnabled()) {
+ if (source == ShortcutSource.KEYBOARD || isTalkBackShortcutGestureEnabled()) {
isTalkbackAlreadyEnabled = !isTalkbackAlreadyEnabled;
AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
- isTalkbackAlreadyEnabled);
+ isTalkbackAlreadyEnabled, userId);
// log stem triple press telemetry if it's a talkback enabled event.
- if (isTalkbackAlreadyEnabled) {
+ if (source == ShortcutSource.GESTURE && isTalkbackAlreadyEnabled) {
logStemTriplePressAccessibilityTelemetry(componentName);
}
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 892af6bec534..cc31bb17dc9d 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -368,6 +368,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* belongs to.
*/
boolean isCallerVirtualDeviceOwner(int displayId, int callingUid);
+
+ /**
+ * Returns whether the display with the given ID is trusted.
+ */
+ boolean isDisplayTrusted(int displayId);
}
/**
@@ -754,11 +759,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @param focusedToken Client window token that currently has focus. This is where the key
* event will normally go.
* @param event The key event.
- * @param policyFlags The policy flags associated with the key.
- * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
- * The caller is responsible for recycling the key event.
+ * @return true if the unhandled key is intercepted by the policy.
*/
- KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags);
+ boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken);
/**
* Called when the top focused display is changed.
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index af1ad13f1d15..04dbd1fea5d6 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -25,6 +25,7 @@ import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
import static android.view.KeyEvent.KEYCODE_POWER;
import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+import static com.android.server.power.feature.flags.Flags.perDisplayWakeByTouch;
import android.annotation.Nullable;
import android.content.Context;
@@ -107,13 +108,14 @@ class WindowWakeUpPolicy {
/**
* Wakes up from a key event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
* @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
+ boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
@@ -126,22 +128,31 @@ class WindowWakeUpPolicy {
&& mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
return true;
}
- wakeUp(
- eventTime,
- keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
- keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ if (perDisplayWakeByTouch()) {
+ wakeUp(
+ displayId,
+ eventTime,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ } else {
+ wakeUp(
+ eventTime,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ }
return true;
}
/**
* Wakes up from a motion event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+ boolean wakeUpFromMotion(int displayId, long eventTime, int source, boolean isDown) {
if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
return false;
@@ -150,7 +161,11 @@ class WindowWakeUpPolicy {
&& mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
return true;
}
- wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ if (perDisplayWakeByTouch()) {
+ wakeUp(displayId, eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ } else {
+ wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ }
return true;
}
@@ -237,4 +252,12 @@ class WindowWakeUpPolicy {
private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
}
+
+ /** Wakes up given display. */
+ private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
+ // If we're given an invalid display id to wake, fall back to waking default display
+ final int displayIdToWake =
+ displayId == Display.INVALID_DISPLAY ? Display.DEFAULT_DISPLAY : displayId;
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayIdToWake);
+ }
}
diff --git a/services/core/java/com/android/server/power/FrameworkStatsLogger.java b/services/core/java/com/android/server/power/FrameworkStatsLogger.java
new file mode 100644
index 000000000000..78ad30548075
--- /dev/null
+++ b/services/core/java/com/android/server/power/FrameworkStatsLogger.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.os.WorkSource.WorkChain;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+public class FrameworkStatsLogger {
+ public enum WakelockEventType {
+ ACQUIRE,
+ RELEASE
+ }
+
+ /** Log WakelockStateChanged push atom without a WorkChain. */
+ public void wakelockStateChanged(
+ int ownerUid, String tag, int powerManagerWakeLockLevel, WakelockEventType eventType) {
+ int event =
+ (eventType == WakelockEventType.ACQUIRE)
+ ? FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE
+ : FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE;
+ FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ ownerUid,
+ null,
+ powerManagerWakeLockLevel,
+ tag,
+ event,
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED__PROCESS_STATE__PROCESS_STATE_UNKNOWN);
+ }
+
+ /** Log WakelockStateChanged push atom with a WorkChain. */
+ public void wakelockStateChanged(
+ String tag, WorkChain wc, int powerManagerWakeLockLevel, WakelockEventType eventType) {
+ int event =
+ (eventType == WakelockEventType.ACQUIRE)
+ ? FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE
+ : FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE;
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ wc.getUids(),
+ wc.getTags(),
+ powerManagerWakeLockLevel,
+ tag,
+ event,
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED__PROCESS_STATE__PROCESS_STATE_UNKNOWN);
+ }
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 303828f94e8a..0c3c46c75eee 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -33,6 +33,7 @@ import android.media.RingtoneManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.IWakeLockCallback;
@@ -48,11 +49,13 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.WindowManagerPolicyConstants;
import com.android.internal.annotations.VisibleForTesting;
@@ -65,10 +68,12 @@ 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.power.FrameworkStatsLogger.WakelockEventType;
import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -163,6 +168,7 @@ public class Notifier {
}
private final SparseArray<Interactivity> mInteractivityByGroupId = new SparseArray<>();
+ private SparseBooleanArray mDisplayInteractivities = new SparseBooleanArray();
// The current global interactive state. This is set as soon as an interactive state
// transition begins so as to capture the reason that it happened. At some point
@@ -193,6 +199,9 @@ public class Notifier {
private final PowerManagerFlags mFlags;
+ private final BatteryStatsInternal mBatteryStatsInternal;
+ private final FrameworkStatsLogger mFrameworkStatsLogger;
+
public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
@@ -239,6 +248,14 @@ public class Notifier {
} catch (RemoteException ex) { }
FrameworkStatsLog.write(FrameworkStatsLog.INTERACTIVE_STATE_CHANGED,
FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON);
+
+ if (mFlags.isMoveWscLoggingToNotifierEnabled()) {
+ mBatteryStatsInternal = mInjector.getBatteryStatsInternal();
+ mFrameworkStatsLogger = mInjector.getFrameworkStatsLogger();
+ } else {
+ mBatteryStatsInternal = null;
+ mFrameworkStatsLogger = null;
+ }
}
/**
@@ -275,6 +292,7 @@ public class Notifier {
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
+ logWakelockStateChanged(flags, tag, ownerUid, workSource, WakelockEventType.ACQUIRE);
notifyWakeLockListener(callback, tag, true, ownerUid, ownerPid, flags, workSource,
packageName, historyTag);
if (!mFlags.improveWakelockLatency()) {
@@ -378,6 +396,10 @@ public class Notifier {
+ ", workSource=" + newWorkSource);
}
+ logWakelockStateChanged(flags, tag, ownerUid, workSource, WakelockEventType.RELEASE);
+ logWakelockStateChanged(
+ newFlags, newTag, newOwnerUid, newWorkSource, WakelockEventType.ACQUIRE);
+
final boolean unimportantForLogging = newOwnerUid == Process.SYSTEM_UID
&& (newFlags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
try {
@@ -423,6 +445,7 @@ public class Notifier {
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
+ logWakelockStateChanged(flags, tag, ownerUid, workSource, WakelockEventType.RELEASE);
notifyWakeLockListener(callback, tag, false, ownerUid, ownerPid, flags, workSource,
packageName, historyTag);
if (!mFlags.improveWakelockLatency()) {
@@ -512,8 +535,17 @@ public class Notifier {
}
// Start input as soon as we start waking up or going to sleep.
- mInputManagerInternal.setInteractive(interactive);
mInputMethodManagerInternal.setInteractive(interactive);
+ if (!mFlags.isPerDisplayWakeByTouchEnabled()) {
+ // Since wakefulness is a global property in original logic, all displays should
+ // be set to the same interactive state, matching system's global wakefulness
+ SparseBooleanArray displayInteractivities = new SparseBooleanArray();
+ int[] displayIds = mDisplayManagerInternal.getDisplayIds().toArray();
+ for (int displayId : displayIds) {
+ displayInteractivities.put(displayId, interactive);
+ }
+ mInputManagerInternal.setDisplayInteractivities(displayInteractivities);
+ }
// Notify battery stats.
try {
@@ -680,6 +712,42 @@ public class Notifier {
}
/**
+ * Update the interactivities of the displays in given DisplayGroup.
+ *
+ * @param groupId The group id of the DisplayGroup to update display interactivities for.
+ */
+ private void updateDisplayInteractivities(int groupId, boolean interactive) {
+ final int[] displayIds = mDisplayManagerInternal.getDisplayIdsForGroup(groupId);
+ for (int displayId : displayIds) {
+ mDisplayInteractivities.put(displayId, interactive);
+ }
+
+ }
+
+ private void resetDisplayInteractivities() {
+ final SparseArray<int[]> displaysByGroupId =
+ mDisplayManagerInternal.getDisplayIdsByGroupsIds();
+ SparseBooleanArray newDisplayInteractivities = new SparseBooleanArray();
+ for (int i = 0; i < displaysByGroupId.size(); i++) {
+ final int groupId = displaysByGroupId.keyAt(i);
+ for (int displayId : displaysByGroupId.get(groupId)) {
+ // If we already know display interactivity, use that
+ if (mDisplayInteractivities.indexOfKey(displayId) > 0) {
+ newDisplayInteractivities.put(
+ displayId, mDisplayInteractivities.get(displayId));
+ } else { // If display is new to Notifier, use the power group's interactive value
+ final Interactivity groupInteractivity = mInteractivityByGroupId.get(groupId);
+ // If group Interactivity hasn't been initialized, assume group is interactive
+ final boolean groupInteractive =
+ groupInteractivity == null || groupInteractivity.isInteractive;
+ newDisplayInteractivities.put(displayId, groupInteractive);
+ }
+ }
+ }
+ mDisplayInteractivities = newDisplayInteractivities;
+ }
+
+ /**
* Called when an individual PowerGroup changes wakefulness.
*/
public void onGroupWakefulnessChangeStarted(int groupId, int wakefulness, int changeReason,
@@ -707,6 +775,12 @@ public class Notifier {
handleEarlyInteractiveChange(groupId);
mWakefulnessSessionObserver.onWakefulnessChangeStarted(groupId, wakefulness,
changeReason, eventTime);
+
+ // Update input on which displays are interactive
+ if (mFlags.isPerDisplayWakeByTouchEnabled()) {
+ updateDisplayInteractivities(groupId, isInteractive);
+ mInputManagerInternal.setDisplayInteractivities(mDisplayInteractivities);
+ }
}
}
@@ -718,6 +792,20 @@ public class Notifier {
public void onGroupRemoved(int groupId) {
mInteractivityByGroupId.remove(groupId);
mWakefulnessSessionObserver.removePowerGroup(groupId);
+ if (mFlags.isPerDisplayWakeByTouchEnabled()) {
+ resetDisplayInteractivities();
+ mInputManagerInternal.setDisplayInteractivities(mDisplayInteractivities);
+ }
+ }
+
+ /**
+ * Called when a PowerGroup has been changed.
+ */
+ public void onGroupChanged() {
+ if (mFlags.isPerDisplayWakeByTouchEnabled()) {
+ resetDisplayInteractivities();
+ mInputManagerInternal.setDisplayInteractivities(mDisplayInteractivities);
+ }
}
/**
@@ -1191,6 +1279,44 @@ public class Notifier {
}
}
+ private void logWakelockStateChanged(
+ int flags,
+ String tag,
+ int ownerUid,
+ WorkSource workSource,
+ WakelockEventType eventType) {
+ if (mBatteryStatsInternal == null) {
+ return;
+ }
+ final int type = flags & PowerManager.WAKE_LOCK_LEVEL_MASK;
+ if (workSource == null || workSource.isEmpty()) {
+ final int mappedUid = mBatteryStatsInternal.getOwnerUid(ownerUid);
+ mFrameworkStatsLogger.wakelockStateChanged(mappedUid, tag, type, eventType);
+ } else {
+ for (int i = 0; i < workSource.size(); ++i) {
+ final int mappedUid = mBatteryStatsInternal.getOwnerUid(workSource.getUid(i));
+ mFrameworkStatsLogger.wakelockStateChanged(mappedUid, tag, type, eventType);
+ }
+
+ List<WorkChain> workChains = workSource.getWorkChains();
+ if (workChains != null) {
+ for (WorkChain workChain : workChains) {
+ WorkChain mappedWorkChain = new WorkChain();
+ // Cache getUids() and getTags() because they make an arraycopy.
+ int[] uids = workChain.getUids();
+ String[] tags = workChain.getTags();
+
+ for (int i = 0; i < workChain.getSize(); ++i) {
+ final int mappedUid = mBatteryStatsInternal.getOwnerUid(uids[i]);
+ mappedWorkChain.addNode(mappedUid, tags[i]);
+ }
+ mFrameworkStatsLogger.wakelockStateChanged(
+ tag, mappedWorkChain, type, eventType);
+ }
+ }
+ }
+ }
+
public interface Injector {
/**
* Gets the current time in millis
@@ -1206,9 +1332,15 @@ public class Notifier {
* Gets the AppOpsManager system service
*/
AppOpsManager getAppOpsManager(Context context);
+
+ /** Gets the BatteryStatsInternal object */
+ BatteryStatsInternal getBatteryStatsInternal();
+
+ /** Get the FrameworkStatsLogger object */
+ FrameworkStatsLogger getFrameworkStatsLogger();
}
- static class RealInjector implements Injector {
+ class RealInjector implements Injector {
@Override
public long currentTimeMillis() {
return System.currentTimeMillis();
@@ -1223,5 +1355,15 @@ public class Notifier {
public AppOpsManager getAppOpsManager(Context context) {
return context.getSystemService(AppOpsManager.class);
}
+
+ @Override
+ public BatteryStatsInternal getBatteryStatsInternal() {
+ return LocalServices.getService(BatteryStatsInternal.class);
+ }
+
+ @Override
+ public FrameworkStatsLogger getFrameworkStatsLogger() {
+ return new FrameworkStatsLogger();
+ }
}
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 2e8a0c678f48..01a2045df426 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -42,6 +42,9 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.power.feature.PowerManagerFlags;
/**
* Used to store power related requests to every display in a
@@ -55,6 +58,11 @@ public class PowerGroup {
private static final String TAG = PowerGroup.class.getSimpleName();
private static final boolean DEBUG = false;
+ /**
+ * Indicates that the default dim/sleep timeouts should be used.
+ */
+ private static final long INVALID_TIMEOUT = -1;
+
@VisibleForTesting
final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
private final PowerGroupListener mWakefulnessListener;
@@ -62,6 +70,8 @@ public class PowerGroup {
private final DisplayManagerInternal mDisplayManagerInternal;
private final boolean mSupportsSandman;
private final int mGroupId;
+ private final PowerManagerFlags mFeatureFlags;
+
/** True if DisplayManagerService has applied all the latest display states that were requested
* for this group. */
private boolean mReady;
@@ -82,10 +92,18 @@ public class PowerGroup {
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
private long mLastSleepTime;
+ /** The last reason that woke the power group. */
+ private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;
+ /** The last reason that put the power group to sleep. */
+ private @PowerManager.GoToSleepReason int mLastSleepReason =
+ PowerManager.GO_TO_SLEEP_REASON_UNKNOWN;
+
+ private final long mDimDuration;
+ private final long mScreenOffTimeout;
PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier,
DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready,
- boolean supportsSandman, long eventTime) {
+ boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
mGroupId = groupId;
mWakefulnessListener = wakefulnessListener;
mNotifier = notifier;
@@ -95,10 +113,36 @@ public class PowerGroup {
mSupportsSandman = supportsSandman;
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
+ mFeatureFlags = featureFlags;
+
+ long dimDuration = INVALID_TIMEOUT;
+ long screenOffTimeout = INVALID_TIMEOUT;
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && mGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+ VirtualDeviceManagerInternal vdm =
+ LocalServices.getService(VirtualDeviceManagerInternal.class);
+ if (vdm != null) {
+ int[] displayIds = mDisplayManagerInternal.getDisplayIdsForGroup(mGroupId);
+ if (displayIds != null && displayIds.length > 0) {
+ int deviceId = vdm.getDeviceIdForDisplayId(displayIds[0]);
+ if (vdm.isValidVirtualDeviceId(deviceId)) {
+ dimDuration = vdm.getDimDurationMillisForDeviceId(deviceId);
+ screenOffTimeout = vdm.getScreenOffTimeoutMillisForDeviceId(deviceId);
+ if (dimDuration > 0 && dimDuration > screenOffTimeout) {
+ // If the dim duration is set, cap it to the screen off timeout.
+ dimDuration = screenOffTimeout;
+ }
+ }
+ }
+ }
+ }
+ mDimDuration = dimDuration;
+ mScreenOffTimeout = screenOffTimeout;
}
PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier,
- DisplayManagerInternal displayManagerInternal, long eventTime) {
+ DisplayManagerInternal displayManagerInternal, long eventTime,
+ PowerManagerFlags featureFlags) {
mGroupId = Display.DEFAULT_DISPLAY_GROUP;
mWakefulnessListener = wakefulnessListener;
mNotifier = notifier;
@@ -108,6 +152,17 @@ public class PowerGroup {
mSupportsSandman = true;
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
+ mFeatureFlags = featureFlags;
+ mDimDuration = INVALID_TIMEOUT;
+ mScreenOffTimeout = INVALID_TIMEOUT;
+ }
+
+ long getScreenOffTimeoutOverrideLocked(long defaultScreenOffTimeout) {
+ return mScreenOffTimeout == INVALID_TIMEOUT ? defaultScreenOffTimeout : mScreenOffTimeout;
+ }
+
+ long getScreenDimDurationOverrideLocked(long defaultScreenDimDuration) {
+ return mDimDuration == INVALID_TIMEOUT ? defaultScreenDimDuration : mDimDuration;
}
long getLastWakeTimeLocked() {
@@ -138,8 +193,14 @@ public class PowerGroup {
setLastPowerOnTimeLocked(eventTime);
setIsPoweringOnLocked(true);
mLastWakeTime = eventTime;
+ if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+ mLastWakeReason = reason;
+ }
} else if (isInteractive(mWakefulness) && !isInteractive(newWakefulness)) {
mLastSleepTime = eventTime;
+ if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+ mLastSleepReason = reason;
+ }
}
mWakefulness = newWakefulness;
mWakefulnessListener.onWakefulnessChangedLocked(mGroupId, mWakefulness, eventTime,
@@ -393,37 +454,51 @@ public class PowerGroup {
return false;
}
- @VisibleForTesting
- int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
+ // TODO: create and use more specific policy reasons, beyond the ones that correlate to
+ // interactivity state
+ private void updateScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
boolean bootCompleted, boolean screenBrightnessBoostInProgress,
boolean brightWhenDozing) {
final int wakefulness = getWakefulnessLocked();
final int wakeLockSummary = getWakeLockSummaryLocked();
- if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
- return DisplayPowerRequest.POLICY_OFF;
+ int policyReason = Display.STATE_REASON_DEFAULT_POLICY;
+ int policy = Integer.MAX_VALUE; // do not set to real policy to start with.
+ if (quiescent) {
+ policy = DisplayPowerRequest.POLICY_OFF;
+ } else if (wakefulness == WAKEFULNESS_ASLEEP) {
+ policy = DisplayPowerRequest.POLICY_OFF;
+ policyReason = sleepReasonToDisplayStateReason(mLastSleepReason);
} else if (wakefulness == WAKEFULNESS_DOZING) {
if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
- return DisplayPowerRequest.POLICY_DOZE;
- }
- if (dozeAfterScreenOff) {
- return DisplayPowerRequest.POLICY_OFF;
- }
- if (brightWhenDozing) {
- return DisplayPowerRequest.POLICY_BRIGHT;
+ policy = DisplayPowerRequest.POLICY_DOZE;
+ } else if (dozeAfterScreenOff) {
+ policy = DisplayPowerRequest.POLICY_OFF;
+ } else if (brightWhenDozing) {
+ policy = DisplayPowerRequest.POLICY_BRIGHT;
}
// Fall through and preserve the current screen policy if not configured to
// bright when dozing or doze after screen off. This causes the screen off transition
// to be skipped.
}
- if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
- || !bootCompleted
- || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
- || screenBrightnessBoostInProgress) {
- return DisplayPowerRequest.POLICY_BRIGHT;
+ if (policy == Integer.MAX_VALUE) { // policy is not set yet.
+ if (isInteractive(wakefulness)) {
+ policyReason = wakeReasonToDisplayStateReason(mLastWakeReason);
+ }
+ if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
+ || !bootCompleted
+ || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+ || screenBrightnessBoostInProgress) {
+ policy = DisplayPowerRequest.POLICY_BRIGHT;
+ } else {
+ policy = DisplayPowerRequest.POLICY_DIM;
+ }
}
- return DisplayPowerRequest.POLICY_DIM;
+ if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+ mDisplayPowerRequest.policyReason = policyReason;
+ }
+ mDisplayPowerRequest.policy = policy;
}
int getPolicyLocked() {
@@ -439,7 +514,7 @@ public class PowerGroup {
boolean dozeAfterScreenOff, boolean bootCompleted,
boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
boolean brightWhenDozing) {
- mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
+ updateScreenPolicyLocked(quiescent, dozeAfterScreenOff,
bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
@@ -478,6 +553,33 @@ public class PowerGroup {
return ready;
}
+ /** Determines the respective display state reason for a given PowerManager WakeReason. */
+ private static int wakeReasonToDisplayStateReason(@PowerManager.WakeReason int wakeReason) {
+ switch (wakeReason) {
+ case PowerManager.WAKE_REASON_POWER_BUTTON:
+ case PowerManager.WAKE_REASON_WAKE_KEY:
+ return Display.STATE_REASON_KEY;
+ case PowerManager.WAKE_REASON_WAKE_MOTION:
+ return Display.STATE_REASON_MOTION;
+ case PowerManager.WAKE_REASON_TILT:
+ return Display.STATE_REASON_TILT;
+ default:
+ return Display.STATE_REASON_DEFAULT_POLICY;
+ }
+ }
+
+ /** Determines the respective display state reason for a given PowerManager GoToSleepReason. */
+ private static int sleepReasonToDisplayStateReason(
+ @PowerManager.GoToSleepReason int sleepReason) {
+ switch (sleepReason) {
+ case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
+ case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
+ return Display.STATE_REASON_KEY;
+ default:
+ return Display.STATE_REASON_DEFAULT_POLICY;
+ }
+ }
+
protected interface PowerGroupListener {
/**
* Informs the recipient about a wakefulness change of a {@link PowerGroup}.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 71cb8820761f..36bc0b93cd7c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -125,7 +125,6 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.util.Preconditions;
-import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.EventLogTags;
import com.android.server.LockGuard;
import com.android.server.ServiceThread;
@@ -133,6 +132,7 @@ import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
@@ -625,14 +625,6 @@ public final class PowerManagerService extends SystemService
boolean mIsFaceDown = false;
private long mLastFlipTime = 0L;
- // The screen brightness setting override from the window manager
- // to allow the current foreground activity to override the brightness.
- private float mScreenBrightnessOverrideFromWindowManager =
- PowerManager.BRIGHTNESS_INVALID_FLOAT;
-
- // Tag identifying the window/activity that requested the brightness override.
- private CharSequence mScreenBrightnessOverrideFromWmTag = null;
-
// The window manager has determined the user to be inactive via other means.
// Set this to false to disable.
private boolean mUserInactiveOverrideFromWindowManager;
@@ -743,6 +735,7 @@ public final class PowerManagerService extends SystemService
int reason, int uid, int opUid, String opPackageName, String details) {
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
+ mInjector.invalidateIsInteractiveCaches();
if (wakefulness == WAKEFULNESS_AWAKE) {
// Kick user activity to prevent newly awake group from timing out instantly.
// The dream may end without user activity if the dream app crashes / is updated,
@@ -789,7 +782,8 @@ public final class PowerManagerService extends SystemService
WAKEFULNESS_AWAKE,
/* ready= */ false,
supportsSandman,
- mClock.uptimeMillis());
+ mClock.uptimeMillis(),
+ mFeatureFlags);
mPowerGroups.append(groupId, powerGroup);
onPowerGroupEventLocked(DISPLAY_GROUP_ADDED, powerGroup);
}
@@ -1374,7 +1368,8 @@ public final class PowerManagerService extends SystemService
mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP,
new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener,
- mNotifier, mDisplayManagerInternal, mClock.uptimeMillis()));
+ mNotifier, mDisplayManagerInternal, mClock.uptimeMillis(),
+ mFeatureFlags));
DisplayGroupPowerChangeListener displayGroupPowerChangeListener =
new DisplayGroupPowerChangeListener();
mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
@@ -2035,7 +2030,7 @@ public final class PowerManagerService extends SystemService
}
@SuppressWarnings("deprecation")
- private boolean isWakeLockLevelSupportedInternal(int level) {
+ private boolean isWakeLockLevelSupportedInternal(int level, int displayId) {
synchronized (mLock) {
switch (level) {
case PowerManager.PARTIAL_WAKE_LOCK:
@@ -2047,7 +2042,8 @@ public final class PowerManagerService extends SystemService
return true;
case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
- return mSystemReady && mDisplayManagerInternal.isProximitySensorAvailable();
+ return mSystemReady
+ && mDisplayManagerInternal.isProximitySensorAvailable(displayId);
case PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK:
return mSystemReady && mFeatureFlags.isEarlyScreenTimeoutDetectorEnabled()
&& mScreenTimeoutOverridePolicy != null;
@@ -2207,7 +2203,7 @@ public final class PowerManagerService extends SystemService
+ ", groupId=" + powerGroup.getGroupId()
+ ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
- if (mForceSuspendActive || !mSystemReady) {
+ if (mForceSuspendActive || !mSystemReady || (powerGroup == null)) {
return;
}
powerGroup.wakeUpLocked(eventTime, reason, details, uid, opPackageName, opUid,
@@ -2264,7 +2260,6 @@ public final class PowerManagerService extends SystemService
int opUid, String opPackageName, String details) {
mPowerGroups.get(groupId).setWakefulnessLocked(wakefulness, eventTime, uid, reason, opUid,
opPackageName, details);
- mInjector.invalidateIsInteractiveCaches();
}
@SuppressWarnings("deprecation")
@@ -2445,6 +2440,8 @@ public final class PowerManagerService extends SystemService
mClock.uptimeMillis());
} else if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
mNotifier.onGroupRemoved(groupId);
+ } else if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_CHANGED) {
+ mNotifier.onGroupChanged();
}
if (oldWakefulness != newWakefulness) {
@@ -2967,8 +2964,8 @@ public final class PowerManagerService extends SystemService
mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
final long attentiveTimeout = getAttentiveTimeoutLocked();
- final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
- final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+ final long defaultSleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+ final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(defaultSleepTimeout,
attentiveTimeout);
final long defaultScreenDimDuration = getScreenDimDurationLocked(defaultScreenOffTimeout);
@@ -2981,13 +2978,25 @@ public final class PowerManagerService extends SystemService
final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
final int wakefulness = powerGroup.getWakefulnessLocked();
- // The default display screen timeout could be overridden by policy.
+ // The timeouts could be overridden by the power group policy.
long screenOffTimeout = defaultScreenOffTimeout;
long screenDimDuration = defaultScreenDimDuration;
+ long sleepTimeout = defaultSleepTimeout;
+ // TODO(b/376211497): Consolidate the timeout logic for all power groups.
if (powerGroup.getGroupId() == Display.DEFAULT_DISPLAY_GROUP) {
screenOffTimeout =
- getScreenOffTimeoutOverrideLocked(screenOffTimeout, screenDimDuration);
+ getDefaultGroupScreenOffTimeoutOverrideLocked(screenOffTimeout,
+ screenDimDuration);
screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ } else {
+ screenOffTimeout = powerGroup.getScreenOffTimeoutOverrideLocked(screenOffTimeout);
+ screenDimDuration =
+ powerGroup.getScreenDimDurationOverrideLocked(screenDimDuration);
+ if (sleepTimeout > 0 && screenOffTimeout > 0) {
+ // If both sleep and screen off timeouts are set, make sure that the sleep
+ // timeout is not smaller than the screen off one.
+ sleepTimeout = Math.max(sleepTimeout, screenOffTimeout);
+ }
}
if (wakefulness != WAKEFULNESS_ASLEEP) {
@@ -3269,7 +3278,8 @@ public final class PowerManagerService extends SystemService
@VisibleForTesting
@GuardedBy("mLock")
- long getScreenOffTimeoutOverrideLocked(long screenOffTimeout, long screenDimDuration) {
+ long getDefaultGroupScreenOffTimeoutOverrideLocked(long screenOffTimeout,
+ long screenDimDuration) {
long shortestScreenOffTimeout = screenOffTimeout;
if (mScreenTimeoutOverridePolicy != null) {
shortestScreenOffTimeout =
@@ -3645,9 +3655,7 @@ public final class PowerManagerService extends SystemService
// Keep the brightness steady during boot. This requires the
// bootloader brightness and the default brightness to be identical.
screenBrightnessOverride = mScreenBrightnessDefault;
- } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
- screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
- overrideTag = mScreenBrightnessOverrideFromWmTag;
+ overrideTag = "boot";
} else {
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
@@ -3736,14 +3744,6 @@ public final class PowerManagerService extends SystemService
}
@VisibleForTesting
- @GuardedBy("mLock")
- int getDesiredScreenPolicyLocked(int groupId) {
- return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
- mDozeAfterScreenOff, mBootCompleted,
- mScreenBrightnessBoostInProgress, mBrightWhenDozingConfig);
- }
-
- @VisibleForTesting
int getDreamsBatteryLevelDrain() {
return mDreamsBatteryLevelDrain;
}
@@ -3973,6 +3973,9 @@ public final class PowerManagerService extends SystemService
private boolean isInteractiveInternal(int displayId, int uid) {
synchronized (mLock) {
+ if (!mSystemReady) {
+ return isGloballyInteractiveInternal();
+ }
DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
if (displayInfo == null) {
Slog.w(TAG, "Did not find DisplayInfo for displayId " + displayId);
@@ -4436,19 +4439,6 @@ public final class PowerManagerService extends SystemService
}
}
- private void setScreenBrightnessOverrideFromWindowManagerInternal(
- float brightness, CharSequence tag) {
- synchronized (mLock) {
- if (!BrightnessSynchronizer.floatEquals(mScreenBrightnessOverrideFromWindowManager,
- brightness)) {
- mScreenBrightnessOverrideFromWindowManager = brightness;
- mScreenBrightnessOverrideFromWmTag = tag;
- mDirty |= DIRTY_SETTINGS;
- updatePowerStateLocked();
- }
- }
- }
-
private void setUserInactiveOverrideFromWindowManagerInternal() {
synchronized (mLock) {
mUserInactiveOverrideFromWindowManager = true;
@@ -4583,7 +4573,8 @@ public final class PowerManagerService extends SystemService
WAKEFULNESS_AWAKE,
/* ready= */ false,
/* supportsSandman= */ false,
- mClock.uptimeMillis());
+ mClock.uptimeMillis(),
+ mFeatureFlags);
mPowerGroups.append(displayGroupId, powerGroup);
}
mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
@@ -4786,10 +4777,6 @@ public final class PowerManagerService extends SystemService
+ mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
+ isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
- pw.println(" mScreenBrightnessOverrideFromWindowManager="
- + mScreenBrightnessOverrideFromWindowManager);
- pw.println(" mScreenBrightnessOverrideFromWmTag="
- + mScreenBrightnessOverrideFromWmTag);
pw.println(" mUserActivityTimeoutOverrideFromWindowManager="
+ mUserActivityTimeoutOverrideFromWindowManager);
pw.println(" mUserInactiveOverrideFromWindowManager="
@@ -5179,10 +5166,6 @@ public final class PowerManagerService extends SystemService
proto.write(
PowerServiceSettingsAndConfigurationDumpProto
- .SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER,
- mScreenBrightnessOverrideFromWindowManager);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto
.USER_ACTIVITY_TIMEOUT_OVERRIDE_FROM_WINDOW_MANAGER_MS,
mUserActivityTimeoutOverrideFromWindowManager);
proto.write(
@@ -5973,7 +5956,17 @@ public final class PowerManagerService extends SystemService
public boolean isWakeLockLevelSupported(int level) {
final long ident = Binder.clearCallingIdentity();
try {
- return isWakeLockLevelSupportedInternal(level);
+ return isWakeLockLevelSupportedInternal(level, Display.DEFAULT_DISPLAY);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean isWakeLockLevelSupportedWithDisplayId(int level, int displayId) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isWakeLockLevelSupportedInternal(level, displayId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6027,6 +6020,12 @@ public final class PowerManagerService extends SystemService
@Override // Binder call
public void wakeUp(long eventTime, @WakeReason int reason, String details,
String opPackageName) {
+ wakeUpWithDisplayId(eventTime, reason, details, opPackageName, Display.DEFAULT_DISPLAY);
+ }
+
+ @Override // Binder call
+ public void wakeUpWithDisplayId(long eventTime, @WakeReason int reason, String details,
+ String opPackageName, int displayId) {
final long now = mClock.uptimeMillis();
if (eventTime > now) {
Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now);
@@ -6039,13 +6038,14 @@ public final class PowerManagerService extends SystemService
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
+ int displayGroupId = getDisplayGroupId(displayId);
synchronized (mLock) {
if (!mBootCompleted && sQuiescent) {
mDirty |= DIRTY_QUIESCENT;
updatePowerStateLocked();
return;
}
- wakePowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), eventTime,
+ wakePowerGroupLocked(mPowerGroups.get(displayGroupId), eventTime,
reason, details, uid, opPackageName, uid);
}
} finally {
@@ -6099,16 +6099,28 @@ public final class PowerManagerService extends SystemService
}
}
- public float getBrightnessConstraint(int constraint) {
+ @Override
+ public float getBrightnessConstraint(
+ int displayId, @PowerManager.BrightnessConstraint int constraint) {
+ DisplayInfo info = null;
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()
+ && mDisplayManagerInternal != null) {
+ info = mDisplayManagerInternal.getDisplayInfo(displayId);
+ }
switch (constraint) {
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM:
- return mScreenBrightnessMinimum;
+ return info != null && isValidBrightnessValue(info.brightnessMinimum)
+ ? info.brightnessMinimum : mScreenBrightnessMinimum;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM:
- return mScreenBrightnessMaximum;
+ return info != null && isValidBrightnessValue(info.brightnessMaximum)
+ ? info.brightnessMaximum : mScreenBrightnessMaximum;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT:
- return mScreenBrightnessDefault;
+ return info != null && isValidBrightnessValue(info.brightnessDefault)
+ ? info.brightnessDefault : mScreenBrightnessDefault;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
- return mScreenBrightnessDim;
+ return android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && info != null && isValidBrightnessValue(info.brightnessDim)
+ ? info.brightnessDim : mScreenBrightnessDim;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
@@ -7105,17 +7117,6 @@ public final class PowerManagerService extends SystemService
@VisibleForTesting
final class LocalService extends PowerManagerInternal {
@Override
- public void setScreenBrightnessOverrideFromWindowManager(
- float screenBrightness, CharSequence tag) {
- if (screenBrightness < PowerManager.BRIGHTNESS_MIN
- || screenBrightness > PowerManager.BRIGHTNESS_MAX) {
- screenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- tag = null;
- }
- setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness, tag);
- }
-
- @Override
public void setDozeOverrideFromDreamManager(
int screenState, int reason, float screenBrightnessFloat, int screenBrightnessInt,
boolean useNormalBrightnessForDoze) {
@@ -7335,4 +7336,12 @@ public final class PowerManagerService extends SystemService
}
}
}
+
+ private int getDisplayGroupId(int displayId) {
+ DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
+ if (displayInfo == null) {
+ return Display.INVALID_DISPLAY_GROUP;
+ }
+ return displayInfo.displayGroupId;
+ }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index f69a017fc45a..35a69a29d16a 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -22,6 +22,8 @@ import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.PowerManagerInternal;
@@ -32,6 +34,9 @@ import android.os.SystemClock;
import android.util.SparseArray;
import android.view.Display;
+import com.android.server.LocalServices;
+import com.android.server.pm.pkg.AndroidPackage;
+
import java.io.PrintWriter;
import java.util.List;
@@ -266,11 +271,18 @@ class PowerManagerShellCommand extends ShellCommand {
ServiceManager.getService(Context.ALARM_SERVICE));
}
try {
- // This command is called by the shell, which has "com.android.shell" as package
- // name.
- pw.println("Schedule an alarm to wakeup in "
- + delayMillis + " ms, on behalf of shell.");
- mAlarmManager.set("com.android.shell",
+ PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage callingPackage =
+ packageManagerInternal.getPackage(Binder.getCallingUid());
+ if (callingPackage == null) {
+ pw.println("Calling uid " + Binder.getCallingUid() + " is not an android"
+ + " package. Cannot schedule a delayed wakeup on behalf of it.");
+ return -1;
+ }
+ pw.println("Schedule an alarm to wakeup in " + delayMillis +
+ " ms, on behalf of " + callingPackage.getPackageName());
+ mAlarmManager.set(callingPackage.getPackageName(),
AlarmManager.RTC_WAKEUP, wakeUpTime,
0, 0, AlarmManager.FLAG_PRIORITIZE,
null, mAlarmListener, "PowerManagerShellCommand", null, null);
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index dc6b1644db4d..42dbb7974fe2 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -31,6 +31,7 @@ import android.content.Context;
import android.hardware.thermal.IThermal;
import android.hardware.thermal.IThermalChangedCallback;
import android.hardware.thermal.TemperatureThreshold;
+import android.hardware.thermal.TemperatureType;
import android.hardware.thermal.ThrottlingSeverity;
import android.hardware.thermal.V1_0.ThermalStatus;
import android.hardware.thermal.V1_0.ThermalStatusCode;
@@ -42,6 +43,7 @@ import android.os.Handler;
import android.os.HwBinder;
import android.os.IBinder;
import android.os.IThermalEventListener;
+import android.os.IThermalHeadroomListener;
import android.os.IThermalService;
import android.os.IThermalStatusListener;
import android.os.PowerManager;
@@ -58,6 +60,7 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.StatsEvent;
import com.android.internal.annotations.GuardedBy;
@@ -95,6 +98,15 @@ public class ThermalManagerService extends SystemService {
/** Input range limits for getThermalHeadroom API */
public static final int MIN_FORECAST_SEC = 0;
public static final int MAX_FORECAST_SEC = 60;
+ public static final int DEFAULT_FORECAST_SECONDS = 10;
+ public static final int HEADROOM_CALLBACK_MIN_INTERVAL_MILLIS = 5000;
+ // headroom to temperature conversion: 3C every 0.1 headroom difference
+ // if no throttling event, the temperature difference should be at least 0.9C (or 0.03 headroom)
+ // to make a callback
+ public static final float HEADROOM_CALLBACK_MIN_DIFFERENCE = 0.03f;
+ // if no throttling event, the threshold headroom difference should be at least 0.01 (or 0.3C)
+ // to make a callback
+ public static final float HEADROOM_THRESHOLD_CALLBACK_MIN_DIFFERENCE = 0.01f;
/** Lock to protect listen list. */
private final Object mLock = new Object();
@@ -112,6 +124,15 @@ public class ThermalManagerService extends SystemService {
private final RemoteCallbackList<IThermalStatusListener> mThermalStatusListeners =
new RemoteCallbackList<>();
+ /** Registered observers of the thermal headroom. */
+ @GuardedBy("mLock")
+ private final RemoteCallbackList<IThermalHeadroomListener> mThermalHeadroomListeners =
+ new RemoteCallbackList<>();
+ @GuardedBy("mLock")
+ private long mLastHeadroomCallbackTimeMillis;
+ @GuardedBy("mLock")
+ private HeadroomCallbackData mLastHeadroomCallbackData = null;
+
/** Current thermal status */
@GuardedBy("mLock")
private int mStatus;
@@ -132,7 +153,38 @@ public class ThermalManagerService extends SystemService {
/** Watches temperatures to forecast when throttling will occur */
@VisibleForTesting
- final TemperatureWatcher mTemperatureWatcher = new TemperatureWatcher();
+ final TemperatureWatcher mTemperatureWatcher;
+
+ private final ThermalHalWrapper.WrapperThermalChangedCallback mWrapperCallback =
+ new ThermalHalWrapper.WrapperThermalChangedCallback() {
+ @Override
+ public void onTemperatureChanged(Temperature temperature) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ThermalManagerService.this.onTemperatureChanged(temperature, true);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onThresholdChanged(TemperatureThreshold threshold) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final HeadroomCallbackData data;
+ synchronized (mTemperatureWatcher.mSamples) {
+ Slog.d(TAG, "Updating skin threshold: " + threshold);
+ mTemperatureWatcher.updateTemperatureThresholdLocked(threshold, true);
+ data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
+ }
+ synchronized (mLock) {
+ checkAndNotifyHeadroomListenersLocked(data);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
private final Context mContext;
@@ -146,9 +198,10 @@ public class ThermalManagerService extends SystemService {
mContext = context;
mHalWrapper = halWrapper;
if (halWrapper != null) {
- halWrapper.setCallback(this::onTemperatureChangedCallback);
+ halWrapper.setCallback(mWrapperCallback);
}
mStatus = Temperature.THROTTLING_NONE;
+ mTemperatureWatcher = new TemperatureWatcher();
}
@Override
@@ -171,19 +224,19 @@ public class ThermalManagerService extends SystemService {
// Connect to HAL and post to listeners.
boolean halConnected = (mHalWrapper != null);
if (!halConnected) {
- mHalWrapper = new ThermalHalAidlWrapper(this::onTemperatureChangedCallback);
+ mHalWrapper = new ThermalHalAidlWrapper(mWrapperCallback);
halConnected = mHalWrapper.connectToHal();
}
if (!halConnected) {
- mHalWrapper = new ThermalHal20Wrapper(this::onTemperatureChangedCallback);
+ mHalWrapper = new ThermalHal20Wrapper(mWrapperCallback);
halConnected = mHalWrapper.connectToHal();
}
if (!halConnected) {
- mHalWrapper = new ThermalHal11Wrapper(this::onTemperatureChangedCallback);
+ mHalWrapper = new ThermalHal11Wrapper(mWrapperCallback);
halConnected = mHalWrapper.connectToHal();
}
if (!halConnected) {
- mHalWrapper = new ThermalHal10Wrapper(this::onTemperatureChangedCallback);
+ mHalWrapper = new ThermalHal10Wrapper(mWrapperCallback);
halConnected = mHalWrapper.connectToHal();
}
if (!halConnected) {
@@ -200,37 +253,84 @@ public class ThermalManagerService extends SystemService {
onTemperatureChanged(temperatures.get(i), false);
}
onTemperatureMapChangedLocked();
- mTemperatureWatcher.updateThresholds();
+ mTemperatureWatcher.getAndUpdateThresholds();
mHalReady.set(true);
}
}
- private void postStatusListener(IThermalStatusListener listener) {
+ @GuardedBy("mLock")
+ private void postStatusListenerLocked(IThermalStatusListener listener) {
final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
try {
listener.onStatusChange(mStatus);
} catch (RemoteException | RuntimeException e) {
- Slog.e(TAG, "Thermal callback failed to call", e);
+ Slog.e(TAG, "Thermal status callback failed to call", e);
}
});
if (!thermalCallbackQueued) {
- Slog.e(TAG, "Thermal callback failed to queue");
+ Slog.e(TAG, "Thermal status callback failed to queue");
}
}
+ @GuardedBy("mLock")
private void notifyStatusListenersLocked() {
final int length = mThermalStatusListeners.beginBroadcast();
try {
for (int i = 0; i < length; i++) {
final IThermalStatusListener listener =
mThermalStatusListeners.getBroadcastItem(i);
- postStatusListener(listener);
+ postStatusListenerLocked(listener);
}
} finally {
mThermalStatusListeners.finishBroadcast();
}
}
+ @GuardedBy("mLock")
+ private void postHeadroomListenerLocked(IThermalHeadroomListener listener,
+ HeadroomCallbackData data) {
+ if (!mHalReady.get()) {
+ return;
+ }
+ final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
+ try {
+ if (Float.isNaN(data.mHeadroom)) {
+ return;
+ }
+ listener.onHeadroomChange(data.mHeadroom, data.mForecastHeadroom,
+ data.mForecastSeconds, data.mHeadroomThresholds);
+ } catch (RemoteException | RuntimeException e) {
+ Slog.e(TAG, "Thermal headroom callback failed to call", e);
+ }
+ });
+ if (!thermalCallbackQueued) {
+ Slog.e(TAG, "Thermal headroom callback failed to queue");
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void checkAndNotifyHeadroomListenersLocked(HeadroomCallbackData data) {
+ if (!data.isSignificantDifferentFrom(mLastHeadroomCallbackData)
+ && System.currentTimeMillis()
+ < mLastHeadroomCallbackTimeMillis + HEADROOM_CALLBACK_MIN_INTERVAL_MILLIS) {
+ // skip notifying the client with similar data within a short period
+ return;
+ }
+ mLastHeadroomCallbackTimeMillis = System.currentTimeMillis();
+ mLastHeadroomCallbackData = data;
+ final int length = mThermalHeadroomListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ final IThermalHeadroomListener listener =
+ mThermalHeadroomListeners.getBroadcastItem(i);
+ postHeadroomListenerLocked(listener, data);
+ }
+ } finally {
+ mThermalHeadroomListeners.finishBroadcast();
+ }
+ }
+
+ @GuardedBy("mLock")
private void onTemperatureMapChangedLocked() {
int newStatus = Temperature.THROTTLING_NONE;
final int count = mTemperatureMap.size();
@@ -246,6 +346,7 @@ public class ThermalManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
private void setStatusLocked(int newStatus) {
if (newStatus != mStatus) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ThermalManagerService.status", newStatus);
@@ -254,18 +355,18 @@ public class ThermalManagerService extends SystemService {
}
}
- private void postEventListenerCurrentTemperatures(IThermalEventListener listener,
+ @GuardedBy("mLock")
+ private void postEventListenerCurrentTemperaturesLocked(IThermalEventListener listener,
@Nullable Integer type) {
- synchronized (mLock) {
- final int count = mTemperatureMap.size();
- for (int i = 0; i < count; i++) {
- postEventListener(mTemperatureMap.valueAt(i), listener,
- type);
- }
+ final int count = mTemperatureMap.size();
+ for (int i = 0; i < count; i++) {
+ postEventListenerLocked(mTemperatureMap.valueAt(i), listener,
+ type);
}
}
- private void postEventListener(Temperature temperature,
+ @GuardedBy("mLock")
+ private void postEventListenerLocked(Temperature temperature,
IThermalEventListener listener,
@Nullable Integer type) {
// Skip if listener registered with a different type
@@ -276,14 +377,15 @@ public class ThermalManagerService extends SystemService {
try {
listener.notifyThrottling(temperature);
} catch (RemoteException | RuntimeException e) {
- Slog.e(TAG, "Thermal callback failed to call", e);
+ Slog.e(TAG, "Thermal event callback failed to call", e);
}
});
if (!thermalCallbackQueued) {
- Slog.e(TAG, "Thermal callback failed to queue");
+ Slog.e(TAG, "Thermal event callback failed to queue");
}
}
+ @GuardedBy("mLock")
private void notifyEventListenersLocked(Temperature temperature) {
final int length = mThermalEventListeners.beginBroadcast();
try {
@@ -292,7 +394,7 @@ public class ThermalManagerService extends SystemService {
mThermalEventListeners.getBroadcastItem(i);
final Integer type =
(Integer) mThermalEventListeners.getBroadcastCookie(i);
- postEventListener(temperature, listener, type);
+ postEventListenerLocked(temperature, listener, type);
}
} finally {
mThermalEventListeners.finishBroadcast();
@@ -322,26 +424,30 @@ public class ThermalManagerService extends SystemService {
}
}
- private void onTemperatureChanged(Temperature temperature, boolean sendStatus) {
+ private void onTemperatureChanged(Temperature temperature, boolean sendCallback) {
shutdownIfNeeded(temperature);
synchronized (mLock) {
Temperature old = mTemperatureMap.put(temperature.getName(), temperature);
if (old == null || old.getStatus() != temperature.getStatus()) {
notifyEventListenersLocked(temperature);
}
- if (sendStatus) {
+ if (sendCallback) {
onTemperatureMapChangedLocked();
}
}
- }
-
- /* HwBinder callback **/
- private void onTemperatureChangedCallback(Temperature temperature) {
- final long token = Binder.clearCallingIdentity();
- try {
- onTemperatureChanged(temperature, true);
- } finally {
- Binder.restoreCallingIdentity(token);
+ if (sendCallback && Flags.allowThermalThresholdsCallback()
+ && temperature.getType() == Temperature.TYPE_SKIN) {
+ final HeadroomCallbackData data;
+ synchronized (mTemperatureWatcher.mSamples) {
+ Slog.d(TAG, "Updating new temperature: " + temperature);
+ mTemperatureWatcher.updateTemperatureSampleLocked(System.currentTimeMillis(),
+ temperature);
+ mTemperatureWatcher.mCachedHeadrooms.clear();
+ data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
+ }
+ synchronized (mLock) {
+ checkAndNotifyHeadroomListenersLocked(data);
+ }
}
}
@@ -383,7 +489,7 @@ public class ThermalManagerService extends SystemService {
return false;
}
// Notify its callback after new client registered.
- postEventListenerCurrentTemperatures(listener, null);
+ postEventListenerCurrentTemperaturesLocked(listener, null);
return true;
} finally {
Binder.restoreCallingIdentity(token);
@@ -399,11 +505,11 @@ public class ThermalManagerService extends SystemService {
synchronized (mLock) {
final long token = Binder.clearCallingIdentity();
try {
- if (!mThermalEventListeners.register(listener, new Integer(type))) {
+ if (!mThermalEventListeners.register(listener, type)) {
return false;
}
// Notify its callback after new client registered.
- postEventListenerCurrentTemperatures(listener, new Integer(type));
+ postEventListenerCurrentTemperaturesLocked(listener, type);
return true;
} finally {
Binder.restoreCallingIdentity(token);
@@ -468,7 +574,7 @@ public class ThermalManagerService extends SystemService {
return false;
}
// Notify its callback after new client registered.
- postStatusListener(listener);
+ postStatusListenerLocked(listener);
return true;
} finally {
Binder.restoreCallingIdentity(token);
@@ -541,11 +647,50 @@ public class ThermalManagerService extends SystemService {
}
@Override
+ public boolean registerThermalHeadroomListener(IThermalHeadroomListener listener) {
+ if (!mHalReady.get()) {
+ return false;
+ }
+ synchronized (mLock) {
+ // Notify its callback after new client registered.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mThermalHeadroomListeners.register(listener)) {
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ final HeadroomCallbackData data;
+ synchronized (mTemperatureWatcher.mSamples) {
+ data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
+ }
+ // Notify its callback after new client registered.
+ synchronized (mLock) {
+ postHeadroomListenerLocked(listener, data);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean unregisterThermalHeadroomListener(IThermalHeadroomListener listener) {
+ synchronized (mLock) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mThermalHeadroomListeners.unregister(listener);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
public float getThermalHeadroom(int forecastSeconds) {
if (!mHalReady.get()) {
FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(),
- FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY,
- Float.NaN, forecastSeconds);
+ FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY,
+ Float.NaN, forecastSeconds);
return Float.NaN;
}
@@ -554,8 +699,8 @@ public class ThermalManagerService extends SystemService {
Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds);
}
FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(),
- FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT,
- Float.NaN, forecastSeconds);
+ FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT,
+ Float.NaN, forecastSeconds);
return Float.NaN;
}
@@ -576,13 +721,10 @@ public class ThermalManagerService extends SystemService {
THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__FEATURE_NOT_SUPPORTED);
throw new UnsupportedOperationException("Thermal headroom thresholds not enabled");
}
- synchronized (mTemperatureWatcher.mSamples) {
- FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED,
- Binder.getCallingUid(),
- THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__SUCCESS);
- return Arrays.copyOf(mTemperatureWatcher.mHeadroomThresholds,
- mTemperatureWatcher.mHeadroomThresholds.length);
- }
+ FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED,
+ Binder.getCallingUid(),
+ THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__SUCCESS);
+ return mTemperatureWatcher.getHeadroomThresholds();
}
@Override
@@ -695,7 +837,7 @@ public class ThermalManagerService extends SystemService {
class ThermalShellCommand extends ShellCommand {
@Override
public int onCommand(String cmd) {
- switch(cmd != null ? cmd : "") {
+ switch (cmd != null ? cmd : "") {
case "inject-temperature":
return runInjectTemperature();
case "override-status":
@@ -924,19 +1066,19 @@ public class ThermalManagerService extends SystemService {
/** Lock to protect HAL handle. */
protected final Object mHalLock = new Object();
- @FunctionalInterface
- interface TemperatureChangedCallback {
- void onValues(Temperature temperature);
+ interface WrapperThermalChangedCallback {
+ void onTemperatureChanged(Temperature temperature);
+ void onThresholdChanged(TemperatureThreshold threshold);
}
/** Temperature callback. */
- protected TemperatureChangedCallback mCallback;
+ protected WrapperThermalChangedCallback mCallback;
/** Cookie for matching the right end point. */
protected static final int THERMAL_HAL_DEATH_COOKIE = 5612;
@VisibleForTesting
- protected void setCallback(TemperatureChangedCallback cb) {
+ protected void setCallback(WrapperThermalChangedCallback cb) {
mCallback = cb;
}
@@ -959,7 +1101,7 @@ public class ThermalManagerService extends SystemService {
List<Temperature> temperatures = getCurrentTemperatures(false, 0);
final int count = temperatures.size();
for (int i = 0; i < count; i++) {
- mCallback.onValues(temperatures.get(i));
+ mCallback.onTemperatureChanged(temperatures.get(i));
}
}
}
@@ -985,31 +1127,42 @@ public class ThermalManagerService extends SystemService {
private IThermal mInstance = null;
/** Callback for Thermal HAL AIDL. */
- private final IThermalChangedCallback mThermalChangedCallback =
+ private final IThermalChangedCallback mThermalCallbackAidl =
new IThermalChangedCallback.Stub() {
- @Override public void notifyThrottling(
- android.hardware.thermal.Temperature temperature)
- throws RemoteException {
+ @Override
+ public void notifyThrottling(
+ android.hardware.thermal.Temperature temperature) {
Temperature svcTemperature = new Temperature(temperature.value,
temperature.type, temperature.name, temperature.throttlingStatus);
final long token = Binder.clearCallingIdentity();
try {
- mCallback.onValues(svcTemperature);
+ mCallback.onTemperatureChanged(svcTemperature);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- @Override public int getInterfaceVersion() throws RemoteException {
- return this.VERSION;
- }
+ @Override
+ public void notifyThresholdChanged(TemperatureThreshold threshold) {
+ if (Flags.allowThermalThresholdsCallback()) {
+ if (threshold.type == TemperatureType.SKIN) {
+ mCallback.onThresholdChanged(threshold);
+ }
+ }
+ }
- @Override public String getInterfaceHash() throws RemoteException {
- return this.HASH;
- }
- };
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return this.VERSION;
+ }
- ThermalHalAidlWrapper(TemperatureChangedCallback callback) {
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return this.HASH;
+ }
+ };
+
+ ThermalHalAidlWrapper(WrapperThermalChangedCallback callback) {
mCallback = callback;
}
@@ -1085,7 +1238,8 @@ public class ThermalManagerService extends SystemService {
}
@Override
- @NonNull protected List<TemperatureThreshold> getTemperatureThresholds(
+ @NonNull
+ protected List<TemperatureThreshold> getTemperatureThresholds(
boolean shouldFilter, int type) {
synchronized (mHalLock) {
final List<TemperatureThreshold> ret = new ArrayList<>();
@@ -1153,7 +1307,7 @@ public class ThermalManagerService extends SystemService {
@VisibleForTesting
void registerThermalChangedCallback() {
try {
- mInstance.registerThermalChangedCallback(mThermalChangedCallback);
+ mInstance.registerThermalChangedCallback(mThermalCallbackAidl);
} catch (IllegalArgumentException | IllegalStateException e) {
Slog.e(TAG, "Couldn't registerThermalChangedCallback due to invalid status",
e);
@@ -1185,7 +1339,7 @@ public class ThermalManagerService extends SystemService {
@GuardedBy("mHalLock")
private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
- ThermalHal10Wrapper(TemperatureChangedCallback callback) {
+ ThermalHal10Wrapper(WrapperThermalChangedCallback callback) {
mCallback = callback;
}
@@ -1317,14 +1471,14 @@ public class ThermalManagerService extends SystemService {
: Temperature.THROTTLING_NONE);
final long token = Binder.clearCallingIdentity();
try {
- mCallback.onValues(thermalSvcTemp);
+ mCallback.onTemperatureChanged(thermalSvcTemp);
} finally {
Binder.restoreCallingIdentity(token);
}
}
};
- ThermalHal11Wrapper(TemperatureChangedCallback callback) {
+ ThermalHal11Wrapper(WrapperThermalChangedCallback callback) {
mCallback = callback;
}
@@ -1455,14 +1609,14 @@ public class ThermalManagerService extends SystemService {
temperature.throttlingStatus);
final long token = Binder.clearCallingIdentity();
try {
- mCallback.onValues(thermalSvcTemp);
+ mCallback.onTemperatureChanged(thermalSvcTemp);
} finally {
Binder.restoreCallingIdentity(token);
}
}
};
- ThermalHal20Wrapper(TemperatureChangedCallback callback) {
+ ThermalHal20Wrapper(WrapperThermalChangedCallback callback) {
mCallback = callback;
}
@@ -1604,14 +1758,68 @@ public class ThermalManagerService extends SystemService {
}
}
+ private static final class HeadroomCallbackData {
+ float mHeadroom;
+ float mForecastHeadroom;
+ int mForecastSeconds;
+ float[] mHeadroomThresholds;
+
+ HeadroomCallbackData(float headroom, float forecastHeadroom, int forecastSeconds,
+ @NonNull float[] headroomThresholds) {
+ mHeadroom = headroom;
+ mForecastHeadroom = forecastHeadroom;
+ mForecastSeconds = forecastSeconds;
+ mHeadroomThresholds = headroomThresholds;
+ }
+
+ private boolean isSignificantDifferentFrom(HeadroomCallbackData other) {
+ if (other == null) return true;
+ // currently this is always the same as DEFAULT_FORECAST_SECONDS, when it's retried
+ // from thermal HAL, we may want to adjust this.
+ if (this.mForecastSeconds != other.mForecastSeconds) return true;
+ if (Math.abs(this.mHeadroom - other.mHeadroom)
+ >= HEADROOM_CALLBACK_MIN_DIFFERENCE) return true;
+ if (Math.abs(this.mForecastHeadroom - other.mForecastHeadroom)
+ >= HEADROOM_CALLBACK_MIN_DIFFERENCE) return true;
+ for (int i = 0; i < this.mHeadroomThresholds.length; i++) {
+ if (Float.isNaN(this.mHeadroomThresholds[i]) != Float.isNaN(
+ other.mHeadroomThresholds[i])) {
+ return true;
+ }
+ if (Math.abs(this.mHeadroomThresholds[i] - other.mHeadroomThresholds[i])
+ >= HEADROOM_THRESHOLD_CALLBACK_MIN_DIFFERENCE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "HeadroomCallbackData[mHeadroom=" + mHeadroom + ", mForecastHeadroom="
+ + mForecastHeadroom + ", mForecastSeconds=" + mForecastSeconds
+ + ", mHeadroomThresholds=" + Arrays.toString(mHeadroomThresholds) + "]";
+ }
+ }
+
@VisibleForTesting
class TemperatureWatcher {
+ private static final int RING_BUFFER_SIZE = 30;
+ private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
+ @VisibleForTesting
+ long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
+
private final Handler mHandler = BackgroundThread.getHandler();
- /** Map of skin temperature sensor name to a corresponding list of samples */
+ /**
+ * Map of skin temperature sensor name to a corresponding list of samples
+ * Updates to the samples should also clear the headroom cache.
+ */
@GuardedBy("mSamples")
@VisibleForTesting
final ArrayMap<String, ArrayList<Sample>> mSamples = new ArrayMap<>();
+ @GuardedBy("mSamples")
+ private final SparseArray<Float> mCachedHeadrooms = new SparseArray<>(2);
/** Map of skin temperature sensor name to the corresponding SEVERE temperature threshold */
@GuardedBy("mSamples")
@@ -1623,51 +1831,52 @@ public class ThermalManagerService extends SystemService {
@GuardedBy("mSamples")
private long mLastForecastCallTimeMillis = 0;
- private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
- @VisibleForTesting
- long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
-
- void updateThresholds() {
+ void getAndUpdateThresholds() {
List<TemperatureThreshold> thresholds =
- mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
+ mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
synchronized (mSamples) {
if (Flags.allowThermalHeadroomThresholds()) {
Arrays.fill(mHeadroomThresholds, Float.NaN);
}
- for (int t = 0; t < thresholds.size(); ++t) {
- TemperatureThreshold threshold = thresholds.get(t);
- if (threshold.hotThrottlingThresholds.length <= ThrottlingSeverity.SEVERE) {
- continue;
- }
- float severeThreshold =
- threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE];
- if (!Float.isNaN(severeThreshold)) {
- mSevereThresholds.put(threshold.name, severeThreshold);
- if (Flags.allowThermalHeadroomThresholds()) {
- for (int severity = ThrottlingSeverity.LIGHT;
- severity <= ThrottlingSeverity.SHUTDOWN; severity++) {
- if (threshold.hotThrottlingThresholds.length > severity) {
- updateHeadroomThreshold(severity,
- threshold.hotThrottlingThresholds[severity],
- severeThreshold);
- }
- }
- }
- }
+ for (final TemperatureThreshold threshold : thresholds) {
+ updateTemperatureThresholdLocked(threshold, false);
}
}
}
// For an older device with multiple SKIN sensors, we will set a severity's headroom
- // threshold based on the minimum value of all as a workaround.
- void updateHeadroomThreshold(int severity, float threshold, float severeThreshold) {
- if (!Float.isNaN(threshold)) {
- synchronized (mSamples) {
+ // threshold based on the minimum value of all as a workaround, unless override.
+ @GuardedBy("mSamples")
+ void updateTemperatureThresholdLocked(TemperatureThreshold threshold, boolean override) {
+ if (threshold.hotThrottlingThresholds.length <= ThrottlingSeverity.SEVERE) {
+ return;
+ }
+ float severeThreshold =
+ threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE];
+ if (Float.isNaN(severeThreshold)) {
+ return;
+ }
+ mSevereThresholds.put(threshold.name, severeThreshold);
+ if (!Flags.allowThermalHeadroomThresholds()) {
+ return;
+ }
+ if (override) {
+ Slog.d(TAG, "Headroom cache cleared on threshold update " + threshold);
+ mCachedHeadrooms.clear();
+ Arrays.fill(mHeadroomThresholds, Float.NaN);
+ }
+ for (int severity = ThrottlingSeverity.LIGHT;
+ severity <= ThrottlingSeverity.SHUTDOWN; severity++) {
+ if (threshold.hotThrottlingThresholds.length > severity) {
+ float t = threshold.hotThrottlingThresholds[severity];
+ if (Float.isNaN(t)) {
+ continue;
+ }
if (severity == ThrottlingSeverity.SEVERE) {
mHeadroomThresholds[severity] = 1.0f;
- return;
+ continue;
}
- float headroom = normalizeTemperature(threshold, severeThreshold);
+ float headroom = normalizeTemperature(t, severeThreshold);
if (Float.isNaN(mHeadroomThresholds[severity])) {
mHeadroomThresholds[severity] = headroom;
} else {
@@ -1678,45 +1887,46 @@ public class ThermalManagerService extends SystemService {
}
}
- private static final int RING_BUFFER_SIZE = 30;
-
- private void updateTemperature() {
+ private void getAndUpdateTemperatureSamples() {
synchronized (mSamples) {
if (SystemClock.elapsedRealtime() - mLastForecastCallTimeMillis
< mInactivityThresholdMillis) {
// Trigger this again after a second as long as forecast has been called more
// recently than the inactivity timeout
- mHandler.postDelayed(this::updateTemperature, 1000);
+ mHandler.postDelayed(this::getAndUpdateTemperatureSamples, 1000);
} else {
// Otherwise, we've been idle for at least 10 seconds, so we should
// shut down
mSamples.clear();
+ mCachedHeadrooms.clear();
return;
}
long now = SystemClock.elapsedRealtime();
- List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
+ final List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
Temperature.TYPE_SKIN);
-
- for (int t = 0; t < temperatures.size(); ++t) {
- Temperature temperature = temperatures.get(t);
-
- // Filter out invalid temperatures. If this results in no values being stored at
- // all, the mSamples.empty() check in getForecast() will catch it.
- if (Float.isNaN(temperature.getValue())) {
- continue;
- }
-
- ArrayList<Sample> samples = mSamples.computeIfAbsent(temperature.getName(),
- k -> new ArrayList<>(RING_BUFFER_SIZE));
- if (samples.size() == RING_BUFFER_SIZE) {
- samples.removeFirst();
- }
- samples.add(new Sample(now, temperature.getValue()));
+ for (Temperature temperature : temperatures) {
+ updateTemperatureSampleLocked(now, temperature);
}
+ mCachedHeadrooms.clear();
}
}
+ @GuardedBy("mSamples")
+ private void updateTemperatureSampleLocked(long timeNow, Temperature temperature) {
+ // Filter out invalid temperatures. If this results in no values being stored at
+ // all, the mSamples.empty() check in getForecast() will catch it.
+ if (Float.isNaN(temperature.getValue())) {
+ return;
+ }
+ ArrayList<Sample> samples = mSamples.computeIfAbsent(temperature.getName(),
+ k -> new ArrayList<>(RING_BUFFER_SIZE));
+ if (samples.size() == RING_BUFFER_SIZE) {
+ samples.removeFirst();
+ }
+ samples.add(new Sample(timeNow, temperature.getValue()));
+ }
+
/**
* Calculates the trend using a linear regression. As the samples are degrees Celsius with
* associated timestamps in milliseconds, the slope is in degrees Celsius per millisecond.
@@ -1769,7 +1979,7 @@ public class ThermalManagerService extends SystemService {
synchronized (mSamples) {
mLastForecastCallTimeMillis = SystemClock.elapsedRealtime();
if (mSamples.isEmpty()) {
- updateTemperature();
+ getAndUpdateTemperatureSamples();
}
// If somehow things take much longer than expected or there are no temperatures
@@ -1794,6 +2004,14 @@ public class ThermalManagerService extends SystemService {
return Float.NaN;
}
+ if (mCachedHeadrooms.contains(forecastSeconds)) {
+ // TODO(b/360486877): replace with metrics
+ Slog.d(TAG,
+ "Headroom forecast in " + forecastSeconds + "s served from cache: "
+ + mCachedHeadrooms.get(forecastSeconds));
+ return mCachedHeadrooms.get(forecastSeconds);
+ }
+
float maxNormalized = Float.NaN;
int noThresholdSampleCount = 0;
for (Map.Entry<String, ArrayList<Sample>> entry : mSamples.entrySet()) {
@@ -1810,6 +2028,12 @@ public class ThermalManagerService extends SystemService {
float currentTemperature = samples.getLast().temperature;
if (samples.size() < MINIMUM_SAMPLE_COUNT) {
+ if (mSamples.size() == 1 && mCachedHeadrooms.contains(0)) {
+ // if only one sensor name exists, then try reading the cache
+ // TODO(b/360486877): replace with metrics
+ Slog.d(TAG, "Headroom forecast cached: " + mCachedHeadrooms.get(0));
+ return mCachedHeadrooms.get(0);
+ }
// Don't try to forecast, just use the latest one we have
float normalized = normalizeTemperature(currentTemperature, threshold);
if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
@@ -1817,8 +2041,10 @@ public class ThermalManagerService extends SystemService {
}
continue;
}
-
- float slope = getSlopeOf(samples);
+ float slope = 0.0f;
+ if (forecastSeconds > 0) {
+ slope = getSlopeOf(samples);
+ }
float normalized = normalizeTemperature(
currentTemperature + slope * forecastSeconds * 1000, threshold);
if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
@@ -1836,10 +2062,28 @@ public class ThermalManagerService extends SystemService {
FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS,
maxNormalized, forecastSeconds);
}
+ mCachedHeadrooms.put(forecastSeconds, maxNormalized);
return maxNormalized;
}
}
+ float[] getHeadroomThresholds() {
+ synchronized (mSamples) {
+ return Arrays.copyOf(mHeadroomThresholds, mHeadroomThresholds.length);
+ }
+ }
+
+ @GuardedBy("mSamples")
+ HeadroomCallbackData getHeadroomCallbackDataLocked() {
+ final HeadroomCallbackData data = new HeadroomCallbackData(
+ getForecast(0),
+ getForecast(DEFAULT_FORECAST_SECONDS),
+ DEFAULT_FORECAST_SECONDS,
+ Arrays.copyOf(mHeadroomThresholds, mHeadroomThresholds.length));
+ Slog.d(TAG, "New headroom callback data: " + data);
+ return data;
+ }
+
@VisibleForTesting
// Since Sample is inside an inner class, we can't make it static
// This allows test code to create Sample objects via ThermalManagerService
@@ -1848,7 +2092,7 @@ public class ThermalManagerService extends SystemService {
}
@VisibleForTesting
- class Sample {
+ static class Sample {
public long time;
public float temperature;
@@ -1856,6 +2100,11 @@ public class ThermalManagerService extends SystemService {
this.time = time;
this.temperature = temperature;
}
+
+ @Override
+ public String toString() {
+ return "Sample[temperature=" + temperature + ", time=" + time + "]";
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
index c6b260288a88..64f0693f14c4 100644
--- a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
+++ b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
@@ -49,6 +49,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAddress;
@@ -70,12 +71,11 @@ import java.lang.annotation.RetentionPolicy;
public class WakefulnessSessionObserver {
private static final String TAG = "WakefulnessSessionObserver";
- private static final int OFF_REASON_UNKNOWN = FrameworkStatsLog
+ static final int OFF_REASON_UNKNOWN = FrameworkStatsLog
.SCREEN_INTERACTIVE_SESSION_REPORTED__INTERACTIVE_STATE_OFF_REASON__UNKNOWN;
- private static final int OFF_REASON_TIMEOUT = FrameworkStatsLog
+ static final int OFF_REASON_TIMEOUT = FrameworkStatsLog
.SCREEN_INTERACTIVE_SESSION_REPORTED__INTERACTIVE_STATE_OFF_REASON__TIMEOUT;
- @VisibleForTesting
- protected static final int OFF_REASON_POWER_BUTTON = FrameworkStatsLog
+ static final int OFF_REASON_POWER_BUTTON = FrameworkStatsLog
.SCREEN_INTERACTIVE_SESSION_REPORTED__INTERACTIVE_STATE_OFF_REASON__POWER_BUTTON;
/**
@@ -90,25 +90,21 @@ public class WakefulnessSessionObserver {
@Retention(RetentionPolicy.SOURCE)
private @interface OffReason {}
- private static final int OVERRIDE_OUTCOME_UNKNOWN = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_UNKNOWN = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__UNKNOWN;
- @VisibleForTesting
- protected static final int OVERRIDE_OUTCOME_TIMEOUT_SUCCESS = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_TIMEOUT_SUCCESS = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__TIMEOUT_SUCCESS;
- @VisibleForTesting
- protected static final int OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__TIMEOUT_USER_INITIATED_REVERT;
- private static final int OVERRIDE_OUTCOME_CANCEL_CLIENT_API_CALL = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_CANCEL_CLIENT_API_CALL = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_CLIENT_API_CALL;
- @VisibleForTesting
- protected static final int OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_USER_INTERACTION;
- @VisibleForTesting
- protected static final int OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_POWER_BUTTON;
- private static final int OVERRIDE_OUTCOME_CANCEL_CLIENT_DISCONNECT = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_CANCEL_CLIENT_DISCONNECT = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_CLIENT_DISCONNECTED;
- private static final int OVERRIDE_OUTCOME_CANCEL_OTHER = FrameworkStatsLog
+ static final int OVERRIDE_OUTCOME_CANCEL_OTHER = FrameworkStatsLog
.SCREEN_TIMEOUT_OVERRIDE_REPORTED__OVERRIDE_OUTCOME__CANCEL_OTHER;
/**
@@ -128,19 +124,15 @@ public class WakefulnessSessionObserver {
@Retention(RetentionPolicy.SOURCE)
private @interface OverrideOutcome {}
- private static final int POLICY_REASON_UNKNOWN = FrameworkStatsLog
+ static final int POLICY_REASON_UNKNOWN = FrameworkStatsLog
.SCREEN_DIM_REPORTED__POLICY_REASON__UNKNOWN;
- @VisibleForTesting
- protected static final int POLICY_REASON_OFF_TIMEOUT = FrameworkStatsLog
+ static final int POLICY_REASON_OFF_TIMEOUT = FrameworkStatsLog
.SCREEN_DIM_REPORTED__POLICY_REASON__OFF_TIMEOUT;
- @VisibleForTesting
- protected static final int POLICY_REASON_OFF_POWER_BUTTON = FrameworkStatsLog
+ static final int POLICY_REASON_OFF_POWER_BUTTON = FrameworkStatsLog
.SCREEN_DIM_REPORTED__POLICY_REASON__OFF_POWER_BUTTON;
- @VisibleForTesting
- protected static final int POLICY_REASON_BRIGHT_UNDIM = FrameworkStatsLog
+ static final int POLICY_REASON_BRIGHT_UNDIM = FrameworkStatsLog
.SCREEN_DIM_REPORTED__POLICY_REASON__BRIGHT_UNDIM;
- @VisibleForTesting
- protected static final int POLICY_REASON_BRIGHT_INITIATED_REVERT = FrameworkStatsLog
+ static final int POLICY_REASON_BRIGHT_INITIATED_REVERT = FrameworkStatsLog
.SCREEN_DIM_REPORTED__POLICY_REASON__BRIGHT_INITIATED_REVERT;
/**
@@ -157,21 +149,18 @@ public class WakefulnessSessionObserver {
@Retention(RetentionPolicy.SOURCE)
private @interface PolicyReason {}
- @VisibleForTesting protected static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER;
- private static final long USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L;
- private static final long SEND_OVERRIDE_TIMEOUT_LOG_THRESHOLD_MILLIS = 1000L;
- @VisibleForTesting
- protected static final long SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS = 500L;
+ static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER;
+ static final long USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L;
+ static final long SEND_OVERRIDE_TIMEOUT_LOG_THRESHOLD_MILLIS = 1000L;
+ static final long SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS = 500L;
- @VisibleForTesting protected static final Object HANDLER_TOKEN = new Object();
+ static final Object HANDLER_TOKEN = new Object();
private Context mContext;
private int mScreenOffTimeoutMs;
private int mOverrideTimeoutMs = 0;
- @VisibleForTesting
- protected final SparseArray<WakefulnessSessionPowerGroup> mPowerGroups = new SparseArray<>();
- @VisibleForTesting
- protected WakefulnessSessionFrameworkStatsLogger mWakefulnessSessionFrameworkStatsLogger;
+ final SparseArray<WakefulnessSessionPowerGroup> mPowerGroups = new SparseArray<>();
+ WakefulnessSessionFrameworkStatsLogger mWakefulnessSessionFrameworkStatsLogger;
private final Clock mClock;
private final Object mLock = new Object();
private final Handler mHandler;
@@ -347,7 +336,8 @@ public class WakefulnessSessionObserver {
writer.println();
}
- private void updateSettingScreenOffTimeout(Context context) {
+ @VisibleForTesting
+ void updateSettingScreenOffTimeout(Context context) {
synchronized (mLock) {
mScreenOffTimeoutMs = Settings.System.getIntForUser(
context.getContentResolver(),
@@ -453,6 +443,7 @@ public class WakefulnessSessionObserver {
return;
}
+ final int screenOffTimeoutMs = getScreenOffTimeout();
mIsInteractive = isInteractive(wakefulness);
if (mIsInteractive) {
mInteractiveStateOnStartTimestamp = eventTime;
@@ -466,7 +457,7 @@ public class WakefulnessSessionObserver {
mPowerGroupId,
OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT,
mOverrideTimeoutMs,
- getScreenOffTimeout());
+ screenOffTimeoutMs);
mSendOverrideTimeoutLogTimestamp = eventTime;
}
mTimeoutOffTimestamp = TIMEOUT_OFF_RESET_TIMESTAMP;
@@ -496,7 +487,7 @@ public class WakefulnessSessionObserver {
mPowerGroupId,
OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON,
mOverrideTimeoutMs,
- getScreenOffTimeout());
+ screenOffTimeoutMs);
mSendOverrideTimeoutLogTimestamp = eventTime;
mTimeoutOverrideReleaseReason = RELEASE_REASON_UNKNOWN; // reset the reason
}
@@ -514,13 +505,12 @@ public class WakefulnessSessionObserver {
// timeout has been done successfully.
if (isInOverrideTimeout()) {
reducedInteractiveStateOnDurationMs =
- getScreenOffTimeout() - mOverrideTimeoutMs;
-
+ screenOffTimeoutMs - mOverrideTimeoutMs;
mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent(
mPowerGroupId,
OVERRIDE_OUTCOME_TIMEOUT_SUCCESS,
mOverrideTimeoutMs,
- getScreenOffTimeout());
+ screenOffTimeoutMs);
mSendOverrideTimeoutLogTimestamp = eventTime;
// Record a timestamp to track if the user initiates to revert from off
@@ -533,13 +523,21 @@ public class WakefulnessSessionObserver {
long interactiveStateOnDurationMs =
eventTime - mInteractiveStateOnStartTimestamp;
- mWakefulnessSessionFrameworkStatsLogger.logSessionEvent(
- mPowerGroupId,
- interactiveStateOffReason,
- interactiveStateOnDurationMs,
- lastUserActivity,
- lastUserActivityDurationMs,
- reducedInteractiveStateOnDurationMs);
+
+ if (reducedInteractiveStateOnDurationMs < screenOffTimeoutMs
+ && reducedInteractiveStateOnDurationMs >= 0) {
+ mWakefulnessSessionFrameworkStatsLogger.logSessionEvent(
+ mPowerGroupId,
+ interactiveStateOffReason,
+ interactiveStateOnDurationMs,
+ lastUserActivity,
+ lastUserActivityDurationMs,
+ reducedInteractiveStateOnDurationMs);
+ } else {
+ Slog.w(TAG, "invalid reducedInteractiveStateOnDurationMs: "
+ + reducedInteractiveStateOnDurationMs);
+ }
+
}
}
@@ -608,6 +606,7 @@ public class WakefulnessSessionObserver {
return;
}
+ final int screenOffTimeoutMs = getScreenOffTimeout();
int dimDurationMs = 0;
int lastUserActivity = mCurrentUserActivityEvent;
int lastUserActivityDurationMs = (int) (eventTime - mCurrentUserActivityTimestamp);
@@ -625,7 +624,7 @@ public class WakefulnessSessionObserver {
lastUserActivity,
lastUserActivityDurationMs,
dimDurationMs,
- mScreenOffTimeoutMs);
+ screenOffTimeoutMs);
mPastDimDurationMs = dimDurationMs;
return;
}
@@ -645,7 +644,7 @@ public class WakefulnessSessionObserver {
lastUserActivity,
lastUserActivityDurationMs,
dimDurationMs,
- mScreenOffTimeoutMs);
+ screenOffTimeoutMs);
mHandler.removeCallbacksAndMessages(HANDLER_TOKEN);
}
@@ -674,7 +673,7 @@ public class WakefulnessSessionObserver {
savedLastUserActivity,
savedLastUserActivityDurationMs,
savedDimDurationMs,
- mScreenOffTimeoutMs);
+ screenOffTimeoutMs);
mPastDimDurationMs = savedDimDurationMs;
}, HANDLER_TOKEN, SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS);
}
@@ -692,7 +691,7 @@ public class WakefulnessSessionObserver {
lastUserActivity,
lastUserActivityDurationMs,
mPastDimDurationMs,
- mScreenOffTimeoutMs);
+ screenOffTimeoutMs);
}
return;
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index 68760aae8d9d..4d63fdf45704 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -959,9 +959,10 @@ public class BatterySaverStateMachine {
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, highlightBundle);
- PendingIntent batterySaverIntent = PendingIntent.getActivity(
+ PendingIntent batterySaverIntent = PendingIntent.getActivityAsUser(
mContext, 0 /* requestCode */, intent,
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ null /* options */, UserHandle.CURRENT);
final String title = res.getString(titleId);
final String summary = res.getString(summaryId);
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index c6ef89dcff69..5cd7dee35e5f 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -42,6 +42,22 @@ public class PowerManagerFlags {
Flags::improveWakelockLatency
);
+ private final FlagState mPerDisplayWakeByTouch = new FlagState(
+ Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH,
+ Flags::perDisplayWakeByTouch
+ );
+
+ private final FlagState mFrameworkWakelockInfo =
+ new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo);
+
+ private final FlagState mPolicyReasonInDisplayPowerRequest = new FlagState(
+ Flags.FLAG_POLICY_REASON_IN_DISPLAY_POWER_REQUEST,
+ Flags::policyReasonInDisplayPowerRequest
+ );
+
+ private final FlagState mMoveWscLoggingToNotifier =
+ new FlagState(Flags.FLAG_MOVE_WSC_LOGGING_TO_NOTIFIER, Flags::moveWscLoggingToNotifier);
+
/** Returns whether early-screen-timeout-detector is enabled on not. */
public boolean isEarlyScreenTimeoutDetectorEnabled() {
return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -55,6 +71,35 @@ public class PowerManagerFlags {
}
/**
+ * @return Whether per-display wake by touch is enabled or not.
+ */
+ public boolean isPerDisplayWakeByTouchEnabled() {
+ return mPerDisplayWakeByTouch.isEnabled();
+ }
+
+ /**
+ * @return Whether FrameworkWakelockInfo atom logging is enabled or not.
+ */
+ public boolean isFrameworkWakelockInfoEnabled() {
+ return mFrameworkWakelockInfo.isEnabled();
+ }
+
+ /**
+ * @return Whether the wakefulness reason is populated in DisplayPowerRequest.
+ */
+ public boolean isPolicyReasonInDisplayPowerRequestEnabled() {
+ return mPolicyReasonInDisplayPowerRequest.isEnabled();
+ }
+
+ /**
+ * @return Whether we move WakelockStateChanged atom logging to Notifier (enabled) or leave it
+ * in BatteryStatsImpl (disabled).
+ */
+ public boolean isMoveWscLoggingToNotifierEnabled() {
+ return mMoveWscLoggingToNotifier.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -62,6 +107,9 @@ public class PowerManagerFlags {
pw.println("PowerManagerFlags:");
pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
pw.println(" " + mImproveWakelockLatency);
+ pw.println(" " + mPerDisplayWakeByTouch);
+ pw.println(" " + mFrameworkWakelockInfo);
+ pw.println(" " + mMoveWscLoggingToNotifier);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 3581b2fad1df..a6948fcbae49 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -17,4 +17,34 @@ flag {
description: "Feature flag for tracking the optimizations to improve the latency of acquiring and releasing a wakelock."
bug: "339590565"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "per_display_wake_by_touch"
+ namespace: "power"
+ description: "Feature flag to enable per-display wake by touch"
+ bug: "343295183"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "framework_wakelock_info"
+ namespace: "power"
+ description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
+ bug: "352602149"
+}
+
+flag {
+ name: "policy_reason_in_display_power_request"
+ namespace: "wear_frameworks"
+ description: "Whether the policy reason is populted in DisplayPowerRequest."
+ bug: "364349703"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "move_wsc_logging_to_notifier"
+ namespace: "power"
+ description: "Feature flag to move logging of WakelockStateChanged atoms from BatteryStatsImpl to Notifier."
+ bug: "352602149"
+}
diff --git a/services/core/java/com/android/server/power/hint/Android.bp b/services/core/java/com/android/server/power/hint/Android.bp
index 6dadf8f16390..da594048da44 100644
--- a/services/core/java/com/android/server/power/hint/Android.bp
+++ b/services/core/java/com/android/server/power/hint/Android.bp
@@ -7,6 +7,15 @@ aconfig_declarations {
],
}
+aconfig_declarations {
+ name: "adpf_flags",
+ package: "android.adpf",
+ container: "system",
+ srcs: [
+ "adpf_flags.aconfig",
+ ],
+}
+
java_aconfig_library {
name: "power_hint_flags_lib",
aconfig_declarations: "power_hint_flags",
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 1346a294b7d8..137ea0617f21 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -21,7 +21,9 @@ import static android.os.Flags.adpfUseFmqChannel;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.power.hint.Flags.adpfSessionTag;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
+import static com.android.server.power.hint.Flags.resetOnForkEnabled;
+import android.adpf.ISessionManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -32,11 +34,18 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.power.ChannelConfig;
+import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
+import android.hardware.power.SupportInfo;
import android.hardware.power.WorkDuration;
import android.os.Binder;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.IHintManager;
@@ -47,6 +56,7 @@ import android.os.PerformanceHintManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SessionCreationConfig;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -65,6 +75,7 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.power.hint.HintManagerService.AppHintSession.SessionModes;
import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
@@ -72,6 +83,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -89,10 +101,15 @@ public final class HintManagerService extends SystemService {
private static final int EVENT_CLEAN_UP_UID = 3;
@VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
+ // The minimum interval between the headroom calls as rate limiting.
+ private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
+ private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
@VisibleForTesting final long mHintSessionPreferredRate;
+ @VisibleForTesting static final int MAX_GRAPHICS_PIPELINE_THREADS_COUNT = 5;
+
// Multi-level map storing all active AppHintSessions.
// First level is keyed by the UID of the client process creating the session.
// Second level is keyed by an IBinder passed from client process. This is used to observe
@@ -120,6 +137,14 @@ public final class HintManagerService extends SystemService {
@GuardedBy("mSessionSnapshotMapLock")
private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap;
+ /*
+ * App UID to Thread mapping.
+ * Thread is a sub class bookkeeping TID, thread mode (especially graphics pipeline mode)
+ * This is to bookkeep and track the thread usage.
+ */
+ @GuardedBy("mThreadsUsageObject")
+ private ArrayMap<Integer, ArraySet<ThreadUsageTracker>> mThreadsUsageMap;
+
/** Lock to protect mActiveSessions and the UidObserver. */
private final Object mLock = new Object();
@@ -135,6 +160,9 @@ public final class HintManagerService extends SystemService {
*/
private final Object mSessionSnapshotMapLock = new Object();
+ /** Lock to protect mThreadsUsageMap. */
+ private final Object mThreadsUsageObject = new Object();
+
@GuardedBy("mNonIsolatedTidsLock")
private final Map<Integer, Set<Long>> mNonIsolatedTids;
@@ -153,16 +181,86 @@ public final class HintManagerService extends SystemService {
private final IPower mPowerHal;
private int mPowerHalVersion;
+ private SupportInfo mSupportInfo = null;
private final PackageManager mPackageManager;
private boolean mUsesFmq;
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
+ private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
+ private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
private Boolean mFMQUsesIntegratedEventFlag = false;
- @VisibleForTesting final IHintManager.Stub mService = new BinderService();
+ private final Object mCpuHeadroomLock = new Object();
+
+ private ISessionManager mSessionManager;
+
+ // this cache tracks the expiration time of the items and performs cleanup on lookup
+ private static class HeadroomCache<K, V> {
+ final List<HeadroomCacheItem> mItemList;
+ final Map<K, HeadroomCacheItem> mKeyItemMap;
+ final long mItemExpDurationMillis;
+
+ class HeadroomCacheItem {
+ long mExpTime;
+ K mKey;
+ V mValue;
+
+ HeadroomCacheItem(K k, V v) {
+ mExpTime = System.currentTimeMillis() + mItemExpDurationMillis;
+ mKey = k;
+ mValue = v;
+ }
+
+ boolean isExpired() {
+ return mExpTime <= System.currentTimeMillis();
+ }
+ }
+
+ void add(K key, V value) {
+ if (mKeyItemMap.containsKey(key)) {
+ final HeadroomCacheItem item = mKeyItemMap.get(key);
+ mItemList.remove(item);
+ }
+ final HeadroomCacheItem item = new HeadroomCacheItem(key, value);
+ mItemList.add(item);
+ mKeyItemMap.put(key, item);
+ }
+
+ V get(K key) {
+ while (!mItemList.isEmpty() && mItemList.getFirst().isExpired()) {
+ mKeyItemMap.remove(mItemList.removeFirst().mKey);
+ }
+ final HeadroomCacheItem item = mKeyItemMap.get(key);
+ if (item == null) {
+ return null;
+ }
+ return item.mValue;
+ }
+
+ HeadroomCache(int size, long expDurationMillis) {
+ mItemList = new LinkedList<>();
+ mKeyItemMap = new ArrayMap<>(size);
+ mItemExpDurationMillis = expDurationMillis;
+ }
+ }
+
+ @GuardedBy("mCpuHeadroomLock")
+ private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache;
+
+ private final Object mGpuHeadroomLock = new Object();
+
+ @GuardedBy("mGpuHeadroomLock")
+ private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache;
+
+ // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal
+ private final int mDefaultCpuHeadroomCalculationWindowMillis;
+ private final int mDefaultGpuHeadroomCalculationWindowMillis;
+
+ @VisibleForTesting
+ final IHintManager.Stub mService = new BinderService();
public HintManagerService(Context context) {
this(context, new Injector());
@@ -187,6 +285,7 @@ public final class HintManagerService extends SystemService {
mActiveSessions = new ArrayMap<>();
mChannelMap = new ArrayMap<>();
mSessionSnapshotMap = new ArrayMap<>();
+ mThreadsUsageMap = new ArrayMap<>();
mNativeWrapper = injector.createNativeWrapper();
mNativeWrapper.halInit();
mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
@@ -198,11 +297,73 @@ public final class HintManagerService extends SystemService {
mUsesFmq = false;
if (mPowerHal != null) {
try {
- mPowerHalVersion = mPowerHal.getInterfaceVersion();
+ mSupportInfo = getSupportInfo();
} catch (RemoteException e) {
throw new IllegalStateException("Could not contact PowerHAL!", e);
}
}
+ mDefaultCpuHeadroomCalculationWindowMillis =
+ new CpuHeadroomParamsInternal().calculationWindowMillis;
+ mDefaultGpuHeadroomCalculationWindowMillis =
+ new GpuHeadroomParamsInternal().calculationWindowMillis;
+ if (mSupportInfo.headroom.isCpuSupported) {
+ mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
+ } else {
+ mCpuHeadroomCache = null;
+ }
+ if (mSupportInfo.headroom.isGpuSupported) {
+ mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
+ } else {
+ mGpuHeadroomCache = null;
+ }
+ }
+
+ SupportInfo getSupportInfo() throws RemoteException {
+ try {
+ mPowerHalVersion = mPowerHal.getInterfaceVersion();
+ if (mPowerHalVersion >= 6) {
+ return mPowerHal.getSupportInfo();
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Could not contact PowerHAL!", e);
+ }
+
+ SupportInfo supportInfo = new SupportInfo();
+ supportInfo.usesSessions = isHintSessionSupported();
+ // Global boosts & modes aren't currently relevant for HMS clients
+ supportInfo.boosts = 0;
+ supportInfo.modes = 0;
+ supportInfo.sessionHints = 0;
+ supportInfo.sessionModes = 0;
+ supportInfo.sessionTags = 0;
+
+ supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+ supportInfo.headroom.isCpuSupported = false;
+ supportInfo.headroom.isGpuSupported = false;
+ if (isHintSessionSupported()) {
+ if (mPowerHalVersion == 4) {
+ // Assume we support the V4 hints & modes unless specified
+ // otherwise; this is to avoid breaking backwards compat
+ // since we historically just assumed they were.
+ supportInfo.sessionHints = 31; // first 5 bits are ones
+ }
+ if (mPowerHalVersion == 5) {
+ // Assume we support the V5 hints & modes unless specified
+ // otherwise; this is to avoid breaking backwards compat
+ // since we historically just assumed they were.
+
+ // Hal V5 has 8 modes, all of which it assumes are supported,
+ // so we represent that by having the first 8 bits set
+ supportInfo.sessionHints = 255; // first 8 bits are ones
+ // Hal V5 has 1 mode which it assumes is supported, so we
+ // represent that by having the first bit set
+ supportInfo.sessionModes = 1;
+ // Hal V5 has 5 tags, all of which it assumes are supported,
+ // so we represent that by having the first 5 bits set
+ supportInfo.sessionTags = 31;
+ }
+ }
+ return supportInfo;
}
private ServiceThread createCleanUpThread() {
@@ -223,6 +384,36 @@ public final class HintManagerService extends SystemService {
}
}
+ private static class ThreadUsageTracker {
+ /*
+ * Thread object for tracking thread usage per UID
+ */
+ int mTid;
+ boolean mIsGraphicsPipeline;
+
+ ThreadUsageTracker(int tid) {
+ mTid = tid;
+ mIsGraphicsPipeline = false;
+ }
+
+ ThreadUsageTracker(int tid, boolean isGraphicsPipeline) {
+ mTid = tid;
+ mIsGraphicsPipeline = isGraphicsPipeline;
+ }
+
+ public int getTid() {
+ return mTid;
+ }
+
+ public boolean isGraphicsPipeline() {
+ return mIsGraphicsPipeline;
+ }
+
+ public void setGraphicsPipeline(boolean isGraphicsPipeline) {
+ mIsGraphicsPipeline = isGraphicsPipeline;
+ }
+ }
+
private class AppHintSessionSnapshot {
/*
* Per-Uid and Per-SessionTag snapshot that tracks metrics including
@@ -234,6 +425,7 @@ public final class HintManagerService extends SystemService {
int mMaxConcurrentSession;
int mMaxThreadCount;
int mPowerEfficientSessionCount;
+ int mGraphicsPipelineSessionCount;
final int mTargetDurationNsCountPQSize = 100;
PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ;
@@ -291,6 +483,7 @@ public final class HintManagerService extends SystemService {
mMaxConcurrentSession = 0;
mMaxThreadCount = 0;
mPowerEfficientSessionCount = 0;
+ mGraphicsPipelineSessionCount = 0;
mTargetDurationNsCountPQ = new PriorityQueue<>(1);
}
@@ -309,6 +502,10 @@ public final class HintManagerService extends SystemService {
mPowerEfficientSessionCount += 1;
}
+ void logGraphicsPipelineSession() {
+ mGraphicsPipelineSessionCount += 1;
+ }
+
void updateThreadCount(int threadCount) {
mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
}
@@ -339,6 +536,10 @@ public final class HintManagerService extends SystemService {
return mPowerEfficientSessionCount;
}
+ int getGraphicsPipelineSessionCount() {
+ return mGraphicsPipelineSessionCount;
+ }
+
long[] targetDurationNsList() {
final int listSize = 5;
long[] targetDurations = new long[listSize];
@@ -351,7 +552,7 @@ public final class HintManagerService extends SystemService {
return targetDurations;
}
}
- private boolean isHalSupported() {
+ private boolean isHintSessionSupported() {
return mHintSessionPreferredRate != -1;
}
@@ -614,6 +815,23 @@ public final class HintManagerService extends SystemService {
for (int i = tokenMap.size() - 1; i >= 0; i--) {
// Will remove the session from tokenMap
ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+ IntArray closedSessionsForSf = new IntArray();
+ // Batch the closure call to SF for all the sessions that die
+ for (int j = sessionSet.size() - 1; j >= 0; j--) {
+ AppHintSession session = sessionSet.valueAt(j);
+ if (session.isTrackedBySf()) {
+ // Mark it as untracked so we don't untrack again on close
+ session.setTrackedBySf(false);
+ closedSessionsForSf.add(session.getSessionId());
+ }
+ }
+ if (mSessionManager != null) {
+ try {
+ mSessionManager.trackedSessionsDied(closedSessionsForSf.toArray());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to communicate with SessionManager");
+ }
+ }
for (int j = sessionSet.size() - 1; j >= 0; j--) {
sessionSet.valueAt(j).close();
}
@@ -737,7 +955,7 @@ public final class HintManagerService extends SystemService {
mLinked = false;
}
if (mConfig != null) {
- try {
+ try {
mPowerHal.closeSessionChannel(mTgid, mUid);
} catch (RemoteException e) {
throw new IllegalStateException("Failed to close session channel!", e);
@@ -981,13 +1199,13 @@ public final class HintManagerService extends SystemService {
}
// returns the first invalid tid or null if not found
- private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) {
+ private Integer checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated) {
// Make sure all tids belongs to the same UID (including isolated UID),
// tids can belong to different application processes.
List<Integer> isolatedPids = null;
for (int i = 0; i < tids.length; i++) {
int tid = tids[i];
- final String[] procStatusKeys = new String[] {
+ final String[] procStatusKeys = new String[]{
"Uid:",
"Tgid:"
};
@@ -1029,24 +1247,40 @@ public final class HintManagerService extends SystemService {
+ " doesn't belong to the calling application " + callingUid;
}
+ private boolean contains(final int[] array, final int target) {
+ for (int element : array) {
+ if (element == target) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@VisibleForTesting
final class BinderService extends IHintManager.Stub {
@Override
public IHintSession createHintSessionWithConfig(@NonNull IBinder token,
- @NonNull int[] tids, long durationNanos, @SessionTag int tag,
- SessionConfig config) {
- if (!isHalSupported()) {
- throw new UnsupportedOperationException("PowerHAL is not supported!");
+ @SessionTag int tag, SessionCreationConfig creationConfig,
+ SessionConfig config) {
+ if (!isHintSessionSupported()) {
+ throw new UnsupportedOperationException("PowerHintSessions are not supported!");
}
java.util.Objects.requireNonNull(token);
- java.util.Objects.requireNonNull(tids);
+ java.util.Objects.requireNonNull(creationConfig.tids);
+
+ final int[] tids = creationConfig.tids;
Preconditions.checkArgument(tids.length != 0, "tids should"
+ " not be empty.");
+
final int callingUid = Binder.getCallingUid();
final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
final long identity = Binder.clearCallingIdentity();
+ final long durationNanos = creationConfig.targetWorkDurationNanos;
+
+ Preconditions.checkArgument(checkGraphicsPipelineValid(creationConfig, callingUid),
+ "not enough of available graphics pipeline thread.");
try {
final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray(tids.length)
: null;
@@ -1057,6 +1291,25 @@ public final class HintManagerService extends SystemService {
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()) {
+ try {
+ for (int tid : tids) {
+ int policy = Process.getThreadScheduler(tid);
+ // If the thread is not using the default scheduling policy (SCHED_OTHER),
+ // we don't change it.
+ if (policy != Process.SCHED_OTHER) {
+ continue;
+ }
+ // set the SCHED_RESET_ON_FORK flag.
+ int prio = Process.getThreadPriority(tid);
+ Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0);
+ Process.setThreadPriority(tid, prio);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids "
+ + Arrays.toString(tids), e);
+ }
+ }
if (adpfSessionTag() && tag == SessionTag.APP) {
// If the category of the app is a game,
@@ -1111,9 +1364,9 @@ public final class HintManagerService extends SystemService {
}
}
- final long sessionId = config.id != -1 ? config.id : halSessionPtr;
+ final long sessionIdForTracing = config.id != -1 ? config.id : halSessionPtr;
logPerformanceHintSessionAtom(
- callingUid, sessionId, durationNanos, tids, tag);
+ callingUid, sessionIdForTracing, durationNanos, tids, tag);
synchronized (mSessionSnapshotMapLock) {
// Update session snapshot upon session creation
@@ -1121,9 +1374,14 @@ public final class HintManagerService extends SystemService {
.computeIfAbsent(tag, k -> new AppHintSessionSnapshot())
.updateUponSessionCreation(tids.length, durationNanos);
}
+ AppHintSession hs = null;
synchronized (mLock) {
- AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids,
- token, halSessionPtr, durationNanos);
+ Integer configId = null;
+ if (config.id != -1) {
+ configId = new Integer((int) config.id);
+ }
+ hs = new AppHintSession(callingUid, callingTgid, tag, tids,
+ token, halSessionPtr, durationNanos, configId);
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
mActiveSessions.get(callingUid);
if (tokenMap == null) {
@@ -1137,9 +1395,34 @@ public final class HintManagerService extends SystemService {
}
sessionSet.add(hs);
mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid);
+ }
- return hs;
+ if (hs != null) {
+ boolean isGraphicsPipeline = false;
+ if (creationConfig.modesToEnable != null) {
+ for (int sessionMode : creationConfig.modesToEnable) {
+ if (sessionMode == SessionModes.GRAPHICS_PIPELINE.ordinal()) {
+ isGraphicsPipeline = true;
+ }
+ hs.setMode(sessionMode, true);
+ }
+ }
+
+ if (creationConfig.layerTokens != null
+ && creationConfig.layerTokens.length > 0) {
+ hs.associateToLayers(creationConfig.layerTokens);
+ }
+
+ synchronized (mThreadsUsageObject) {
+ mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>());
+ ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
+ for (int i = 0; i < tids.length; ++i) {
+ threadsSet.add(new ThreadUsageTracker(tids[i], isGraphicsPipeline));
+ }
+ }
}
+
+ return hs;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1176,9 +1459,8 @@ public final class HintManagerService extends SystemService {
removeChannelItem(callingTgid, callingUid);
};
- @Override
- public long getHintSessionPreferredRate() {
- return mHintSessionPreferredRate;
+ public int getMaxGraphicsPipelineThreadsCount() {
+ return MAX_GRAPHICS_PIPELINE_THREADS_COUNT;
}
@Override
@@ -1194,12 +1476,138 @@ public final class HintManagerService extends SystemService {
}
@Override
+ public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) {
+ if (!mSupportInfo.headroom.isCpuSupported) {
+ throw new UnsupportedOperationException();
+ }
+ final CpuHeadroomParams halParams = new CpuHeadroomParams();
+ halParams.tids = new int[]{Binder.getCallingPid()};
+ halParams.calculationType = params.calculationType;
+ halParams.calculationWindowMillis = params.calculationWindowMillis;
+ if (params.usesDeviceHeadroom) {
+ halParams.tids = new int[]{};
+ } else if (params.tids != null && params.tids.length > 0) {
+ if (params.tids.length > 5) {
+ throw new IllegalArgumentException(
+ "More than 5 TIDs is requested: " + params.tids.length);
+ }
+ if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
+ final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+ for (int tid : params.tids) {
+ if (Process.getThreadGroupLeader(tid) != tgid) {
+ throw new SecurityException("TID " + tid
+ + " doesn't belong to the calling process with pid "
+ + tgid);
+ }
+ }
+ }
+ halParams.tids = params.tids;
+ }
+ if (halParams.calculationWindowMillis
+ == mDefaultCpuHeadroomCalculationWindowMillis) {
+ synchronized (mCpuHeadroomLock) {
+ final CpuHeadroomResult res = mCpuHeadroomCache.get(halParams);
+ if (res != null) return res;
+ }
+ }
+ // return from HAL directly
+ try {
+ final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
+ if (result == null) {
+ Slog.wtf(TAG, "CPU headroom from Power HAL is invalid");
+ return null;
+ }
+ if (halParams.calculationWindowMillis
+ == mDefaultCpuHeadroomCalculationWindowMillis) {
+ synchronized (mCpuHeadroomLock) {
+ mCpuHeadroomCache.add(halParams, result);
+ }
+ }
+ return result;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
+ return null;
+ }
+ }
+
+ @Override
+ public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) {
+ if (!mSupportInfo.headroom.isGpuSupported) {
+ throw new UnsupportedOperationException();
+ }
+ final GpuHeadroomParams halParams = new GpuHeadroomParams();
+ halParams.calculationType = params.calculationType;
+ halParams.calculationWindowMillis = params.calculationWindowMillis;
+ if (halParams.calculationWindowMillis
+ == mDefaultGpuHeadroomCalculationWindowMillis) {
+ synchronized (mGpuHeadroomLock) {
+ final GpuHeadroomResult res = mGpuHeadroomCache.get(halParams);
+ if (res != null) return res;
+ }
+ }
+ // return from HAL directly
+ try {
+ final GpuHeadroomResult headroom = mPowerHal.getGpuHeadroom(halParams);
+ if (headroom == null) {
+ Slog.wtf(TAG, "GPU headroom from Power HAL is invalid");
+ return null;
+ }
+ if (halParams.calculationWindowMillis
+ == mDefaultGpuHeadroomCalculationWindowMillis) {
+ synchronized (mGpuHeadroomLock) {
+ mGpuHeadroomCache.add(halParams, headroom);
+ }
+ }
+ return headroom;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get GPU headroom from Power HAL", e);
+ return null;
+ }
+ }
+
+ @Override
+ public long getCpuHeadroomMinIntervalMillis() {
+ if (!mSupportInfo.headroom.isCpuSupported) {
+ throw new UnsupportedOperationException();
+ }
+ return mSupportInfo.headroom.cpuMinIntervalMillis;
+ }
+
+ @Override
+ public long getGpuHeadroomMinIntervalMillis() {
+ if (!mSupportInfo.headroom.isGpuSupported) {
+ throw new UnsupportedOperationException();
+ }
+ return mSupportInfo.headroom.gpuMinIntervalMillis;
+ }
+
+ @Override
+ public void passSessionManagerBinder(IBinder sessionManager) {
+ // Ensure caller is internal
+ if (Process.myUid() != Binder.getCallingUid()) {
+ return;
+ }
+ mSessionManager = ISessionManager.Stub.asInterface(sessionManager);
+ }
+
+ public IHintManager.HintManagerClientData
+ registerClient(@NonNull IHintManager.IHintManagerClient clientBinder) {
+ IHintManager.HintManagerClientData out = new IHintManager.HintManagerClientData();
+ out.preferredRateNanos = mHintSessionPreferredRate;
+ out.maxGraphicsPipelineThreads = getMaxGraphicsPipelineThreadsCount();
+ out.powerHalVersion = mPowerHalVersion;
+ out.supportInfo = mSupportInfo;
+ return out;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
- pw.println("HAL Support: " + isHalSupported());
+ pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT);
+ pw.println("Hint Session Support: " + isHintSessionSupported());
pw.println("Active Sessions:");
synchronized (mLock) {
for (int i = 0; i < mActiveSessions.size(); i++) {
@@ -1215,6 +1623,63 @@ public final class HintManagerService extends SystemService {
}
}
}
+ pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis);
+ pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis);
+ try {
+ CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
+ params.usesDeviceHeadroom = true;
+ CpuHeadroomResult ret = getCpuHeadroom(params);
+ pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
+ } catch (Exception e) {
+ Slog.d(TAG, "Failed to dump CPU headroom", e);
+ pw.println("CPU headroom: N/A");
+ }
+ try {
+ GpuHeadroomResult ret = getGpuHeadroom(new GpuHeadroomParamsInternal());
+ pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
+ } catch (Exception e) {
+ Slog.d(TAG, "Failed to dump GPU headroom", e);
+ pw.println("GPU headroom: N/A");
+ }
+ }
+
+ private boolean checkGraphicsPipelineValid(SessionCreationConfig creationConfig, int uid) {
+ if (creationConfig.modesToEnable == null) {
+ return true;
+ }
+ boolean setGraphicsPipeline = false;
+ for (int modeToEnable : creationConfig.modesToEnable) {
+ if (modeToEnable == SessionModes.GRAPHICS_PIPELINE.ordinal()) {
+ setGraphicsPipeline = true;
+ }
+ }
+ if (!setGraphicsPipeline) {
+ return true;
+ }
+
+ synchronized (mThreadsUsageObject) {
+ // count used graphics pipeline threads for the calling UID
+ // consider the case that new tids are overlapping with in session tids
+ ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(uid);
+ if (threadsSet == null) {
+ return true;
+ }
+
+ final int newThreadCount = creationConfig.tids.length;
+ int graphicsPipelineThreadCount = 0;
+ for (ThreadUsageTracker t : threadsSet) {
+ // count graphics pipeline threads in use
+ // and exclude overlapping ones
+ if (t.isGraphicsPipeline()) {
+ graphicsPipelineThreadCount++;
+ if (contains(creationConfig.tids, t.getTid())) {
+ graphicsPipelineThreadCount--;
+ }
+ }
+ }
+ return graphicsPipelineThreadCount + newThreadCount
+ <= MAX_GRAPHICS_PIPELINE_THREADS_COUNT;
+ }
}
private void logPerformanceHintSessionAtom(int uid, long sessionId,
@@ -1247,16 +1712,21 @@ public final class HintManagerService extends SystemService {
protected boolean mUpdateAllowedByProcState;
protected int[] mNewThreadIds;
protected boolean mPowerEfficient;
+ protected boolean mGraphicsPipeline;
protected boolean mHasBeenPowerEfficient;
+ protected boolean mHasBeenGraphicsPipeline;
protected boolean mShouldForcePause;
+ protected Integer mSessionId;
+ protected boolean mTrackedBySF;
- private enum SessionModes {
+ enum SessionModes {
POWER_EFFICIENCY,
+ GRAPHICS_PIPELINE,
};
protected AppHintSession(
int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
- long halSessionPtr, long durationNanos) {
+ long halSessionPtr, long durationNanos, Integer sessionId) {
mUid = uid;
mPid = pid;
mTag = sessionTag;
@@ -1266,8 +1736,12 @@ public final class HintManagerService extends SystemService {
mTargetDurationNanos = durationNanos;
mUpdateAllowedByProcState = true;
mPowerEfficient = false;
+ mGraphicsPipeline = false;
mHasBeenPowerEfficient = false;
+ mHasBeenGraphicsPipeline = false;
mShouldForcePause = false;
+ mSessionId = sessionId;
+ mTrackedBySF = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowedByProcState(allowed);
try {
@@ -1282,11 +1756,9 @@ public final class HintManagerService extends SystemService {
boolean updateHintAllowedByProcState(boolean allowed) {
synchronized (this) {
if (allowed && !mUpdateAllowedByProcState && !mShouldForcePause) {
- Slogf.e(TAG, "ADPF IS GETTING RESUMED? UID: " + mUid + " TAG: " + mTag);
resume();
}
if (!allowed && mUpdateAllowedByProcState) {
- Slogf.e(TAG, "ADPF IS GETTING PAUSED? UID: " + mUid + " TAG: " + mTag);
pause();
}
mUpdateAllowedByProcState = allowed;
@@ -1359,6 +1831,19 @@ public final class HintManagerService extends SystemService {
} catch (NoSuchElementException ignored) {
Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
}
+ if (mTrackedBySF) {
+ if (mSessionManager != null) {
+ try {
+ mSessionManager.trackedSessionsDied(new int[]{mSessionId});
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Could not communicate with SessionManager", e);
+ }
+ mTrackedBySF = false;
+ } else {
+ Slog.e(TAG, "SessionManager is null but there are tracked sessions");
+ }
+ }
}
synchronized (mLock) {
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
@@ -1389,6 +1874,25 @@ public final class HintManagerService extends SystemService {
}
sessionSnapshot.updateUponSessionClose();
}
+
+ if (mGraphicsPipeline) {
+ synchronized (mThreadsUsageObject) {
+ ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(mUid);
+ if (threadsSet == null) {
+ Slogf.w(TAG, "Threads Set is null for uid " + mUid);
+ return;
+ }
+ // remove all tids associated with this session
+ for (int i = 0; i < threadsSet.size(); ++i) {
+ if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) {
+ threadsSet.removeAt(i);
+ }
+ }
+ if (threadsSet.isEmpty()) {
+ mThreadsUsageMap.remove(mUid);
+ }
+ }
+ }
if (powerhintThreadCleanup()) {
synchronized (mNonIsolatedTidsLock) {
final int[] tids = getTidsInternal();
@@ -1416,6 +1920,24 @@ public final class HintManagerService extends SystemService {
}
}
+ @Override
+ public void associateToLayers(IBinder[] layerTokens) {
+ synchronized (this) {
+ if (mSessionManager != null && mSessionId != null && layerTokens != null) {
+ // Sf only untracks a session when it dies
+ if (layerTokens.length > 0) {
+ mTrackedBySF = true;
+ }
+ try {
+ mSessionManager.associateSessionToLayers(mSessionId, mUid, layerTokens);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Could not communicate with SessionManager", e);
+ }
+ }
+ }
+ }
+
public void setThreads(@NonNull int[] tids) {
setThreadsInternal(tids, true);
}
@@ -1425,6 +1947,33 @@ public final class HintManagerService extends SystemService {
throw new IllegalArgumentException("Thread id list can't be empty.");
}
+
+ final int callingUid = Binder.getCallingUid();
+ if (mGraphicsPipeline) {
+ synchronized (mThreadsUsageObject) {
+ // replace original tids with new tids
+ ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
+ int graphicsPipelineThreadCount = 0;
+ if (threadsSet != null) {
+ // We count the graphics pipeline threads that are
+ // *not* in this session, since those in this session
+ // will be replaced. Then if the count plus the new tids
+ // is over max available graphics pipeline threads we raise
+ // an exception.
+ for (ThreadUsageTracker t : threadsSet) {
+ if (t.isGraphicsPipeline() && !contains(mThreadIds, t.getTid())) {
+ graphicsPipelineThreadCount++;
+ }
+ }
+ if (graphicsPipelineThreadCount + tids.length
+ > MAX_GRAPHICS_PIPELINE_THREADS_COUNT) {
+ throw new IllegalArgumentException(
+ "Not enough available graphics pipeline threads.");
+ }
+ }
+ }
+ }
+
synchronized (this) {
if (mHalSessionPtr == 0) {
return;
@@ -1436,7 +1985,6 @@ public final class HintManagerService extends SystemService {
return;
}
if (checkTid) {
- final int callingUid = Binder.getCallingUid();
final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray() : null;
final long identity = Binder.clearCallingIdentity();
@@ -1449,6 +1997,25 @@ public final class HintManagerService extends SystemService {
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()) {
+ try {
+ for (int tid : tids) {
+ int policy = Process.getThreadScheduler(tid);
+ // If the thread is not using the default scheduling policy (SCHED_OTHER),
+ // we don't change it.
+ if (policy != Process.SCHED_OTHER) {
+ continue;
+ }
+ // set the SCHED_RESET_ON_FORK flag.
+ int prio = Process.getThreadPriority(tid);
+ Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0);
+ Process.setThreadPriority(tid, prio);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids "
+ + Arrays.toString(tids), e);
+ }
+ }
if (powerhintThreadCleanup()) {
synchronized (mNonIsolatedTidsLock) {
for (int i = nonIsolated.size() - 1; i >= 0; i--) {
@@ -1463,6 +2030,23 @@ public final class HintManagerService extends SystemService {
}
}
mNativeWrapper.halSetThreads(mHalSessionPtr, tids);
+
+ synchronized (mThreadsUsageObject) {
+ // replace old tids with new ones
+ ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
+ if (threadsSet == null) {
+ mThreadsUsageMap.put(callingUid, new ArraySet<ThreadUsageTracker>());
+ threadsSet = mThreadsUsageMap.get(callingUid);
+ }
+ for (int i = 0; i < threadsSet.size(); ++i) {
+ if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) {
+ threadsSet.removeAt(i);
+ }
+ }
+ for (int tid : tids) {
+ threadsSet.add(new ThreadUsageTracker(tid, mGraphicsPipeline));
+ }
+ }
mThreadIds = tids;
mNewThreadIds = null;
// if the update is allowed but the session is force paused by tid clean up, then
@@ -1524,26 +2108,49 @@ public final class HintManagerService extends SystemService {
+ " greater than zero.");
if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) {
mPowerEfficient = enabled;
+ } else if (mode == SessionModes.GRAPHICS_PIPELINE.ordinal()) {
+ mGraphicsPipeline = enabled;
}
mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
}
- if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) {
- if (!mHasBeenPowerEfficient) {
- mHasBeenPowerEfficient = true;
- synchronized (mSessionSnapshotMapLock) {
- ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
- mSessionSnapshotMap.get(mUid);
- if (sessionSnapshots == null) {
- Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
- return;
+ if (enabled) {
+ if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) {
+ if (!mHasBeenPowerEfficient) {
+ mHasBeenPowerEfficient = true;
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid
+ + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.logPowerEfficientSession();
}
- AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
- if (sessionSnapshot == null) {
- Slogf.w(TAG, "Session snapshot is null for uid " + mUid
- + " and tag " + mTag);
- return;
+ }
+ } else if (mode == SessionModes.GRAPHICS_PIPELINE.ordinal()) {
+ if (!mHasBeenGraphicsPipeline) {
+ mHasBeenGraphicsPipeline = true;
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid
+ + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.logGraphicsPipelineSession();
}
- sessionSnapshot.logPowerEfficientSession();
}
}
}
@@ -1570,14 +2177,37 @@ public final class HintManagerService extends SystemService {
}
}
+ public boolean isGraphicsPipeline() {
+ synchronized (this) {
+ return mGraphicsPipeline;
+ }
+ }
+
public int getUid() {
return mUid;
}
+ public boolean isTrackedBySf() {
+ synchronized (this) {
+ return mTrackedBySF;
+ }
+ }
+
+ public void setTrackedBySf(boolean tracked) {
+ synchronized (this) {
+ mTrackedBySF = tracked;
+ }
+ }
+
+
public int getTag() {
return mTag;
}
+ public Integer getSessionId() {
+ return mSessionId;
+ }
+
public long getTargetDurationNs() {
synchronized (this) {
return mTargetDurationNanos;
@@ -1657,6 +2287,7 @@ public final class HintManagerService extends SystemService {
pw.println(prefix + "SessionAllowedByProcState: " + mUpdateAllowedByProcState);
pw.println(prefix + "SessionForcePaused: " + mShouldForcePause);
pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false"));
+ pw.println(prefix + "GraphicsPipeline: " + (mGraphicsPipeline ? "true" : "false"));
}
}
diff --git a/services/core/java/com/android/server/power/hint/adpf_flags.aconfig b/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
new file mode 100644
index 000000000000..97d34836c70c
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
@@ -0,0 +1,15 @@
+# New location for generic ADPF flags across the system
+# This will be moved to the top-level frameworks/base adpf library
+# once it lands
+
+package: "android.adpf"
+container: "system"
+
+flag {
+ name: "adpf_viewrootimpl_action_down_boost"
+ is_exported: true
+ namespace: "game"
+ description: "Guards boosting on touch in ViewRootImpl."
+ is_fixed_read_only: true
+ bug: "360345939"
+}
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index 55afa05f66fe..c231f5a6caec 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -14,3 +14,17 @@ flag {
description: "Feature flag for adding session tag to hint session atom"
bug: "345011125"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "game"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
+
+flag {
+ name: "cpu_headroom_affinity_check"
+ namespace: "game"
+ description: "Check affinity on CPU headroom."
+ bug: "346604998"
+}
diff --git a/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
new file mode 100644
index 000000000000..c0a06fcfdeaa
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AccumulatedBatteryUsageStatsSection.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class AccumulatedBatteryUsageStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "accumulated-battery-usage-stats";
+ public static final long ID = Long.MAX_VALUE;
+
+ private final BatteryUsageStats.Builder mBatteryUsageStats;
+
+ AccumulatedBatteryUsageStatsSection(BatteryUsageStats.Builder batteryUsageStats) {
+ super(TYPE);
+ mBatteryUsageStats = batteryUsageStats;
+ }
+
+ public BatteryUsageStats.Builder getBatteryUsageStatsBuilder() {
+ return mBatteryUsageStats;
+ }
+
+ @Override
+ public void write(TypedXmlSerializer serializer) throws IOException {
+ mBatteryUsageStats.build().writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mBatteryUsageStats.build().dump(ipw, "");
+ }
+
+ @Override
+ public void close() {
+ mBatteryUsageStats.discard();
+ }
+
+ static class Reader implements PowerStatsSpan.SectionReader {
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return new AccumulatedBatteryUsageStatsSection(
+ BatteryUsageStats.createBuilderFromXml(parser));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryChargeCalculator.java b/services/core/java/com/android/server/power/stats/BatteryChargeCalculator.java
index c2e230ec8058..cc05630d037e 100644
--- a/services/core/java/com/android/server/power/stats/BatteryChargeCalculator.java
+++ b/services/core/java/com/android/server/power/stats/BatteryChargeCalculator.java
@@ -38,13 +38,7 @@ public class BatteryChargeCalculator extends PowerCalculator {
builder.setDischargePercentage(
batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED));
- int batteryCapacityMah = batteryStats.getLearnedBatteryCapacity() / 1000;
- if (batteryCapacityMah <= 0) {
- batteryCapacityMah = batteryStats.getMinLearnedBatteryCapacity() / 1000;
- if (batteryCapacityMah <= 0) {
- batteryCapacityMah = batteryStats.getEstimatedBatteryCapacity();
- }
- }
+ int batteryCapacityMah = batteryStats.getBatteryCapacity();
builder.setBatteryCapacity(batteryCapacityMah);
final double dischargedPowerLowerBoundMah =
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 8311034c0298..f90da644c0ce 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -27,12 +27,11 @@ import android.net.wifi.WifiManager;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.Bundle;
+import android.os.Handler;
import android.os.OutcomeReceiver;
import android.os.Parcelable;
-import android.os.Process;
import android.os.SynchronousResultReceiver;
import android.os.SystemClock;
-import android.os.ThreadLocalWorkSource;
import android.os.connectivity.WifiActivityEnergyInfo;
import android.power.PowerStatsInternal;
import android.telephony.ModemActivityInfo;
@@ -50,11 +49,7 @@ import com.android.server.LocalServices;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -85,29 +80,23 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
// stop running.
public static final int UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000;
- private final ScheduledExecutorService mExecutorService =
- Executors.newSingleThreadScheduledExecutor(
- (ThreadFactory) r -> {
- Thread t = new Thread(
- () -> {
- ThreadLocalWorkSource.setUid(Process.myUid());
- r.run();
- },
- "batterystats-worker");
- t.setPriority(Thread.NORM_PRIORITY);
- return t;
- });
+ // Various types of sync, passed to Handler
+ private static final int SYNC_UPDATE = 1;
+ private static final int SYNC_WAKELOCK_CHANGE = 2;
+ private static final int SYNC_BATTERY_LEVEL_CHANGE = 3;
+ private static final int SYNC_PROCESS_STATE_CHANGE = 4;
+ private static final int SYNC_USER_REMOVAL = 5;
+
+ private final Handler mHandler;
@GuardedBy("mStats")
private final BatteryStatsImpl mStats;
@GuardedBy("this")
+ @ExternalUpdateFlag
private int mUpdateFlags = 0;
@GuardedBy("this")
- private Future<?> mCurrentFuture = null;
-
- @GuardedBy("this")
private String mCurrentReason = null;
@GuardedBy("this")
@@ -125,15 +114,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
@GuardedBy("this")
private boolean mUseLatestStates = true;
- @GuardedBy("this")
- private Future<?> mWakelockChangesUpdate;
-
- @GuardedBy("this")
- private Future<?> mBatteryLevelSync;
-
- @GuardedBy("this")
- private Future<?> mProcessStateSync;
-
// If both mStats and mWorkerLock need to be synchronized, mWorkerLock must be acquired first.
private final Object mWorkerLock = new Object();
@@ -190,14 +170,15 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
}
}
- public BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
- this(new Injector(context), stats);
+ public BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats, Handler handler) {
+ this(new Injector(context), stats, handler);
}
@VisibleForTesting
- BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats) {
+ BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats, Handler handler) {
mInjector = injector;
mStats = stats;
+ mHandler = handler;
}
public void systemServicesReady() {
@@ -249,20 +230,20 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
}
@Override
- public synchronized Future<?> scheduleSync(String reason, int flags) {
- return scheduleSyncLocked(reason, flags);
+ public synchronized void scheduleSync(String reason, @ExternalUpdateFlag int flags) {
+ scheduleSyncLocked(reason, flags);
}
@Override
- public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
- return scheduleSyncLocked("remove-uid", UPDATE_CPU);
+ public synchronized void scheduleCpuSyncDueToRemovedUid(int uid) {
+ scheduleSyncLocked("remove-uid", UPDATE_CPU);
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+ public void scheduleSyncDueToScreenStateChange(@ExternalUpdateFlag int flags, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
synchronized (BatteryExternalStatsWorker.this) {
- if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
+ if (!mHandler.hasMessages(SYNC_UPDATE) || (mUpdateFlags & UPDATE_CPU) == 0) {
mOnBattery = onBattery;
mOnBatteryScreenOff = onBatteryScreenOff;
mUseLatestStates = false;
@@ -270,91 +251,70 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
// always update screen state
mScreenState = screenState;
mPerDisplayScreenStates = perDisplayScreenStates;
- return scheduleSyncLocked("screen-state", flags);
+ scheduleSyncLocked("screen-state", flags);
}
}
@Override
- public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
+ public void scheduleCpuSyncDueToWakelockChange(long delayMillis) {
synchronized (BatteryExternalStatsWorker.this) {
- mWakelockChangesUpdate = scheduleDelayedSyncLocked(mWakelockChangesUpdate,
+ scheduleDelayedSyncLocked(SYNC_WAKELOCK_CHANGE,
() -> {
scheduleSync("wakelock-change", UPDATE_CPU);
scheduleRunnable(() -> mStats.postBatteryNeedsCpuUpdateMsg());
},
delayMillis);
- return mWakelockChangesUpdate;
}
}
@Override
public void cancelCpuSyncDueToWakelockChange() {
- synchronized (BatteryExternalStatsWorker.this) {
- if (mWakelockChangesUpdate != null) {
- mWakelockChangesUpdate.cancel(false);
- mWakelockChangesUpdate = null;
- }
- }
+ mHandler.removeMessages(SYNC_WAKELOCK_CHANGE);
}
@Override
- public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
+ public void scheduleSyncDueToBatteryLevelChange(long delayMillis) {
synchronized (BatteryExternalStatsWorker.this) {
- mBatteryLevelSync = scheduleDelayedSyncLocked(mBatteryLevelSync,
+ scheduleDelayedSyncLocked(SYNC_BATTERY_LEVEL_CHANGE,
() -> scheduleSync("battery-level", UPDATE_ALL),
delayMillis);
- return mBatteryLevelSync;
}
}
@GuardedBy("this")
private void cancelSyncDueToBatteryLevelChangeLocked() {
- if (mBatteryLevelSync != null) {
- mBatteryLevelSync.cancel(false);
- mBatteryLevelSync = null;
- }
+ mHandler.removeMessages(SYNC_BATTERY_LEVEL_CHANGE);
}
@Override
public void scheduleSyncDueToProcessStateChange(int flags, long delayMillis) {
synchronized (BatteryExternalStatsWorker.this) {
- mProcessStateSync = scheduleDelayedSyncLocked(mProcessStateSync,
+ scheduleDelayedSyncLocked(SYNC_PROCESS_STATE_CHANGE,
() -> scheduleSync("procstate-change", flags),
delayMillis);
}
}
public void cancelSyncDueToProcessStateChange() {
- synchronized (BatteryExternalStatsWorker.this) {
- if (mProcessStateSync != null) {
- mProcessStateSync.cancel(false);
- mProcessStateSync = null;
- }
- }
+ mHandler.removeMessages(SYNC_PROCESS_STATE_CHANGE);
}
@Override
- public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
- synchronized (BatteryExternalStatsWorker.this) {
- try {
- // Initial quick clean-up after a user removal
- mExecutorService.schedule(() -> {
- synchronized (mStats) {
- mStats.clearRemovedUserUidsLocked(userId);
- }
- }, UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
+ public void scheduleCleanupDueToRemovedUser(int userId) {
+ // Initial quick clean-up after a user removal
+ mHandler.postDelayed(() -> {
+ synchronized (mStats) {
+ mStats.clearRemovedUserUidsLocked(userId);
+ }
+ }, SYNC_USER_REMOVAL, UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS);
- // Final clean-up after a user removal, to take care of UIDs that were running
- // longer than expected
- return mExecutorService.schedule(() -> {
- synchronized (mStats) {
- mStats.clearRemovedUserUidsLocked(userId);
- }
- }, UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
- } catch (RejectedExecutionException e) {
- return CompletableFuture.failedFuture(e);
+ // Final clean-up after a user removal, to take care of UIDs that were running
+ // longer than expected
+ mHandler.postDelayed(() -> {
+ synchronized (mStats) {
+ mStats.clearRemovedUserUidsLocked(userId);
}
- }
+ }, SYNC_USER_REMOVAL, UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS);
}
/**
@@ -368,42 +328,27 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
* cancel it if needed
*/
@GuardedBy("this")
- private Future<?> scheduleDelayedSyncLocked(Future<?> lastScheduledSync, Runnable syncRunnable,
+ private void scheduleDelayedSyncLocked(int what, Runnable syncRunnable,
long delayMillis) {
- if (mExecutorService.isShutdown()) {
- return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
- }
-
- if (lastScheduledSync != null) {
+ if (mHandler.hasMessages(what)) {
// If there's already a scheduled task, leave it as is if we're trying to
// re-schedule it again with a delay, otherwise cancel and re-schedule it.
if (delayMillis == 0) {
- lastScheduledSync.cancel(false);
+ mHandler.removeMessages(what);
} else {
- return lastScheduledSync;
+ return;
}
}
- try {
- return mExecutorService.schedule(syncRunnable, delayMillis, TimeUnit.MILLISECONDS);
- } catch (RejectedExecutionException e) {
- return CompletableFuture.failedFuture(e);
- }
+ mHandler.postDelayed(syncRunnable, what, delayMillis);
}
- public synchronized Future<?> scheduleWrite() {
- if (mExecutorService.isShutdown()) {
- return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
- }
-
+ /**
+ * Schedule and async writing of battery stats to disk
+ */
+ public synchronized void scheduleWrite() {
scheduleSyncLocked("write", UPDATE_ALL);
- // Since we use a single threaded executor, we can assume the next scheduled task's
- // Future finishes after the sync.
- try {
- return mExecutorService.submit(mWriteTask);
- } catch (RejectedExecutionException e) {
- return CompletableFuture.failedFuture(e);
- }
+ mHandler.post(mWriteTask);
}
/**
@@ -411,34 +356,25 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
* within the task, never wait on the resulting Future. This will result in a deadlock.
*/
public synchronized void scheduleRunnable(Runnable runnable) {
- try {
- mExecutorService.submit(runnable);
- } catch (RejectedExecutionException e) {
- Slog.e(TAG, "Couldn't schedule " + runnable, e);
- }
+ mHandler.post(runnable);
}
public void shutdown() {
- mExecutorService.shutdownNow();
+ mHandler.removeMessages(SYNC_UPDATE);
+ mHandler.removeMessages(SYNC_WAKELOCK_CHANGE);
+ mHandler.removeMessages(SYNC_BATTERY_LEVEL_CHANGE);
+ mHandler.removeMessages(SYNC_PROCESS_STATE_CHANGE);
+ mHandler.removeMessages(SYNC_USER_REMOVAL);
}
@GuardedBy("this")
- private Future<?> scheduleSyncLocked(String reason, int flags) {
- if (mExecutorService.isShutdown()) {
- return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
- }
-
- if (mCurrentFuture == null) {
+ private void scheduleSyncLocked(String reason, @ExternalUpdateFlag int flags) {
+ if (!mHandler.hasMessages(SYNC_UPDATE)) {
mUpdateFlags = flags;
mCurrentReason = reason;
- try {
- mCurrentFuture = mExecutorService.submit(mSyncTask);
- } catch (RejectedExecutionException e) {
- return CompletableFuture.failedFuture(e);
- }
+ mHandler.postDelayed(mSyncTask, SYNC_UPDATE, 0);
}
mUpdateFlags |= flags;
- return mCurrentFuture;
}
public long getLastCollectionTimeStamp() {
@@ -468,7 +404,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
useLatestStates = mUseLatestStates;
mUpdateFlags = 0;
mCurrentReason = null;
- mCurrentFuture = null;
mUseLatestStates = true;
if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
cancelSyncDueToBatteryLevelChangeLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d009fa082d68..0dc8fe19745a 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -145,6 +145,7 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
+import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
@@ -175,7 +176,6 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@@ -294,6 +294,7 @@ public class BatteryStatsImpl extends BatteryStats {
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private final WakelockPowerStatsCollector mWakelockPowerStatsCollector;
private final ScreenPowerStatsCollector mScreenPowerStatsCollector;
private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector;
private final WifiPowerStatsCollector mWifiPowerStatsCollector;
@@ -302,6 +303,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final GnssPowerStatsCollector mGnssPowerStatsCollector;
private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector;
private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
+ private boolean mMoveWscLoggingToNotifierEnabled = false;
+
private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever =
new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() {
@@ -400,6 +403,48 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ private final WakelockPowerStatsCollector.WakelockDurationRetriever mWakelockDurationRetriever =
+ new WakelockPowerStatsCollector.WakelockDurationRetriever() {
+
+ @Override
+ public long getWakelockDurationMillis() {
+ synchronized (BatteryStatsImpl.this) {
+ long batteryUptimeUs = getBatteryUptime(mClock.uptimeMillis() * 1000);
+ long screenOnTimeUs = getScreenOnTime(mClock.elapsedRealtime() * 1000,
+ BatteryStats.STATS_SINCE_CHARGED);
+ return Math.max(0, (batteryUptimeUs - screenOnTimeUs) / 1000);
+ }
+ }
+
+ @Override
+ public void retrieveUidWakelockDuration(Callback callback) {
+ synchronized (BatteryStatsImpl.this) {
+ long rawRealtimeUs = mClock.elapsedRealtime() * 1000;
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ Uid u = mUidStats.valueAt(i);
+ long wakeLockTimeUs = 0;
+ ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
+ u.getWakelockStats();
+ final int wakelockStatsCount = wakelockStats.size();
+ for (int j = 0; j < wakelockStatsCount; j++) {
+ final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(j);
+ BatteryStats.Timer timer = wakelock.getWakeTime(
+ BatteryStats.WAKE_TYPE_PARTIAL);
+ if (timer != null) {
+ wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ }
+ }
+
+ long wakelockTimeMs = wakeLockTimeUs / 1000;
+ if (wakelockTimeMs != 0) {
+ callback.onUidWakelockDuration(u.getUid(), wakelockTimeMs);
+ }
+ }
+ }
+ }
+ };
+
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
}
@@ -509,6 +554,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
private boolean mSaveBatteryUsageStatsOnReset;
+ private boolean mAccumulateBatteryUsageStats;
private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
private PowerStatsStore mPowerStatsStore;
@@ -760,17 +806,16 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
if (u.mChildUids != null) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
+ long[] delta = getCpuTimeInFreqContainer();
int childUidCount = u.mChildUids.size();
for (int j = childUidCount - 1; j >= 0; --j) {
LongArrayMultiStateCounter cpuTimeInFreqCounter =
u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
if (cpuTimeInFreqCounter != null) {
mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
- cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
- onBatteryCounter.addCounts(deltaContainer);
- onBatteryScreenOffCounter.addCounts(deltaContainer);
+ cpuTimeInFreqCounter, elapsedRealtimeMs, delta);
+ onBatteryCounter.addCounts(delta);
+ onBatteryScreenOffCounter.addCounts(delta);
}
}
}
@@ -841,8 +886,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (childUid != null) {
final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
if (counter != null) {
- final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
+ final long[] deltaContainer = getCpuTimeInFreqContainer();
mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
@@ -899,19 +943,38 @@ public class BatteryStatsImpl extends BatteryStats {
public @interface ExternalUpdateFlag {
}
- Future<?> scheduleSync(String reason, int flags);
- Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+ /**
+ * Schedules a sync of kernel metrics in accordance with the specified flags.
+ */
+ void scheduleSync(String reason, @ExternalUpdateFlag int flags);
+
+ /**
+ * Schedules a CPU stats sync after a UID removal.
+ */
+ void scheduleCpuSyncDueToRemovedUid(int uid);
/**
* Schedule a sync because of a screen state change.
*/
- Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+ void scheduleSyncDueToScreenStateChange(@ExternalUpdateFlag int flags, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates);
- Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis);
+
+ /**
+ * Schedules a sync after a wakelock state change
+ */
+ void scheduleCpuSyncDueToWakelockChange(long delayMillis);
+
+ /**
+ * Canceles any pending sync due to a wakelock state change
+ */
void cancelCpuSyncDueToWakelockChange();
- Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
+
+ /**
+ * Schedules a sync caused by the battery level change
+ */
+ void scheduleSyncDueToBatteryLevelChange(long delayMillis);
/** Schedule removal of UIDs corresponding to a removed user */
- Future<?> scheduleCleanupDueToRemovedUser(int userId);
+ void scheduleCleanupDueToRemovedUser(int userId);
/** Schedule a sync because of a process state change */
void scheduleSyncDueToProcessStateChange(int flags, long delayMillis);
}
@@ -1673,7 +1736,7 @@ public class BatteryStatsImpl extends BatteryStats {
private long mBatteryTimeToFullSeconds = -1;
- private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq;
+ private long[] mTmpCpuTimeInFreq;
/**
* Times spent by the system server threads handling incoming binder requests.
@@ -2009,7 +2072,7 @@ public class BatteryStatsImpl extends BatteryStats {
private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector,
ScreenPowerStatsCollector.Injector, MobileRadioPowerStatsCollector.Injector,
WifiPowerStatsCollector.Injector, BluetoothPowerStatsCollector.Injector,
- EnergyConsumerPowerStatsCollector.Injector {
+ EnergyConsumerPowerStatsCollector.Injector, WakelockPowerStatsCollector.Injector {
private PackageManager mPackageManager;
private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
private NetworkStatsManager mNetworkStatsManager;
@@ -2126,6 +2189,12 @@ public class BatteryStatsImpl extends BatteryStats {
return () -> mPhoneSignalScanningTimer.getTotalTimeLocked(
mClock.elapsedRealtime() * 1000, STATS_SINCE_CHARGED) / 1000;
}
+
+ @Override
+ public WakelockPowerStatsCollector.WakelockDurationRetriever
+ getWakelockDurationRetriever() {
+ return mWakelockDurationRetriever;
+ }
}
private final PowerStatsCollectorInjector mPowerStatsCollectorInjector =
@@ -5083,10 +5152,15 @@ public class BatteryStatsImpl extends BatteryStats {
Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
-
- mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
- uidStats.mProcessState, true /* acquired */,
- getPowerManagerWakeLockLevel(type));
+ if (!mMoveWscLoggingToNotifierEnabled) {
+ mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
+ uidStats.mProcessState, true /* acquired */,
+ getPowerManagerWakeLockLevel(type));
+ }
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.noteStartWakeLock(
+ mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs);
+ }
}
}
@@ -5129,9 +5203,15 @@ public class BatteryStatsImpl extends BatteryStats {
Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
- mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
- uidStats.mProcessState, false/* acquired */,
- getPowerManagerWakeLockLevel(type));
+ if (!mMoveWscLoggingToNotifierEnabled) {
+ mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
+ uidStats.mProcessState, false/* acquired */,
+ getPowerManagerWakeLockLevel(type));
+ }
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.noteStopWakeLock(
+ mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs);
+ }
if (mappedUid != uid) {
// Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -10874,9 +10954,7 @@ public class BatteryStatsImpl extends BatteryStats {
// Set initial values to all 0. This is a child UID and we want to include
// the entirety of its CPU time-in-freq stats into the parent's stats.
- cpuTimeInFreqCounter.updateValues(
- new LongArrayMultiStateCounter.LongArrayContainer(cpuFreqCount),
- timestampMs);
+ cpuTimeInFreqCounter.updateValues(new long[cpuFreqCount], timestampMs);
} else {
cpuTimeInFreqCounter = null;
}
@@ -10904,7 +10982,6 @@ public class BatteryStatsImpl extends BatteryStats {
// Make special note of Foreground Services
final boolean userAwareService = ActivityManager.isForegroundService(procState);
uidRunningState = BatteryStats.mapToInternalProcessState(procState);
-
if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
return;
}
@@ -10943,8 +11020,7 @@ public class BatteryStatsImpl extends BatteryStats {
final int batteryConsumerProcessState =
mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
- if (mBsi.mSystemReady && mBsi.mPowerStatsCollectorEnabled.get(
- BatteryConsumer.POWER_COMPONENT_CPU)) {
+ if (mBsi.mSystemReady) {
mBsi.mHistory.recordProcessStateChange(elapsedRealtimeMs, uptimeMs, mUid,
batteryConsumerProcessState);
}
@@ -11281,15 +11357,16 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() {
+ private long[] getCpuTimeInFreqContainer() {
if (mTmpCpuTimeInFreq == null) {
- mTmpCpuTimeInFreq =
- new LongArrayMultiStateCounter.LongArrayContainer(
- mCpuScalingPolicies.getScalingStepCount());
+ mTmpCpuTimeInFreq = new long[mCpuScalingPolicies.getScalingStepCount()];
}
return mTmpCpuTimeInFreq;
}
+ WakelockStatsFrameworkEvents mFrameworkEvents = new WakelockStatsFrameworkEvents();
+ PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
+
public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
@NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
@NonNull Handler handler, @Nullable PlatformIdleStateCallback platformIdleStateCallback,
@@ -11345,6 +11422,10 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+ mWakelockPowerStatsCollector = new WakelockPowerStatsCollector(
+ mPowerStatsCollectorInjector);
+ mWakelockPowerStatsCollector.addConsumer(this::recordPowerStats);
+
mScreenPowerStatsCollector = new ScreenPowerStatsCollector(mPowerStatsCollectorInjector);
mScreenPowerStatsCollector.addConsumer(this::recordPowerStats);
@@ -11422,9 +11503,6 @@ public class BatteryStatsImpl extends BatteryStats {
mOnBatteryTimeBase);
}
- mPerDisplayBatteryStats = new DisplayBatteryStats[1];
- mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
-
mInteractiveTimer = new StopwatchTimer(mClock, null, -10, null, mOnBatteryTimeBase);
mPowerSaveModeEnabledTimer = new StopwatchTimer(mClock, null, -2, null,
mOnBatteryTimeBase);
@@ -11970,10 +12048,12 @@ public class BatteryStatsImpl extends BatteryStats {
*/
public void saveBatteryUsageStatsOnReset(
@NonNull BatteryUsageStatsProvider batteryUsageStatsProvider,
- @NonNull PowerStatsStore powerStatsStore) {
+ @NonNull PowerStatsStore powerStatsStore,
+ boolean accumulateBatteryUsageStats) {
mSaveBatteryUsageStatsOnReset = true;
mBatteryUsageStatsProvider = batteryUsageStatsProvider;
mPowerStatsStore = powerStatsStore;
+ mAccumulateBatteryUsageStats = accumulateBatteryUsageStats;
}
@GuardedBy("this")
@@ -12174,29 +12254,27 @@ public class BatteryStatsImpl extends BatteryStats {
return;
}
- final BatteryUsageStats batteryUsageStats;
- synchronized (this) {
- batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this,
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .includeProcessStateData()
- .build());
- }
-
- // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
- // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
- // start time
- long monotonicStartTime =
- mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
- mHandler.post(() -> {
- mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats);
- try {
- batteryUsageStats.close();
- } catch (IOException e) {
- Log.e(TAG, "Cannot close BatteryUsageStats", e);
+ if (mAccumulateBatteryUsageStats) {
+ mBatteryUsageStatsProvider.accumulateBatteryUsageStats(this);
+ } else {
+ final BatteryUsageStats batteryUsageStats;
+ synchronized (this) {
+ batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this,
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includePowerModels()
+ .includeProcessStateData()
+ .build());
}
- });
+
+ // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+ // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+ // start time
+ long monotonicStartTime =
+ mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+ commitMonotonicClock();
+ mPowerStatsStore.storeBatteryUsageStatsAsync(monotonicStartTime, batteryUsageStats);
+ }
}
@GuardedBy("this")
@@ -14809,6 +14887,10 @@ public class BatteryStatsImpl extends BatteryStats {
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU));
mCpuPowerStatsCollector.schedule();
+ mWakelockPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_WAKELOCK));
+ mWakelockPowerStatsCollector.schedule();
+
mScreenPowerStatsCollector.setEnabled(
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN));
mScreenPowerStatsCollector.schedule();
@@ -14849,6 +14931,8 @@ public class BatteryStatsImpl extends BatteryStats {
switch (powerComponent) {
case BatteryConsumer.POWER_COMPONENT_CPU:
return mCpuPowerStatsCollector;
+ case BatteryConsumer.POWER_COMPONENT_WAKELOCK:
+ return mWakelockPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_SCREEN:
return mScreenPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO:
@@ -15310,6 +15394,10 @@ public class BatteryStatsImpl extends BatteryStats {
mMaxLearnedBatteryCapacityUah = Math.max(mMaxLearnedBatteryCapacityUah, chargeFullUah);
mBatteryTimeToFullSeconds = chargeTimeToFullSeconds;
+
+ if (mAccumulateBatteryUsageStats) {
+ mBatteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(this, mHandler);
+ }
}
public static boolean isOnBattery(int plugType, int status) {
@@ -15884,6 +15972,15 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ /**
+ * Controls where the logging of the WakelockStateChanged atom occurs:
+ * true = Notifier, false = BatteryStatsImpl.
+ */
+ public void setMoveWscLoggingToNotifierEnabled(boolean enabled) {
+ synchronized (this) {
+ mMoveWscLoggingToNotifierEnabled = enabled;
+ }
+ }
@GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
@@ -15895,6 +15992,10 @@ public class BatteryStatsImpl extends BatteryStats {
// Already plugged in. Schedule the long plug in alarm.
scheduleNextResetWhilePluggedInCheck();
}
+
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.initialize(context);
+ }
}
}
@@ -16390,6 +16491,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
mCpuPowerStatsCollector.forceSchedule();
+ mWakelockPowerStatsCollector.forceSchedule();
mScreenPowerStatsCollector.forceSchedule();
mMobileRadioPowerStatsCollector.forceSchedule();
mWifiPowerStatsCollector.forceSchedule();
@@ -16414,6 +16516,7 @@ public class BatteryStatsImpl extends BatteryStats {
*/
public void dumpStatsSample(PrintWriter pw) {
mCpuPowerStatsCollector.collectAndDump(pw);
+ mWakelockPowerStatsCollector.collectAndDump(pw);
mScreenPowerStatsCollector.collectAndDump(pw);
mMobileRadioPowerStatsCollector.collectAndDump(pw);
mWifiPowerStatsCollector.collectAndDump(pw);
@@ -16720,7 +16823,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
int NSORPMS = in.readInt();
if (NSORPMS > 10000) {
- throw new ParcelFormatException("File corrupt: too many screen-off rpm stats " + NSORPMS);
+ throw new ParcelFormatException(
+ "File corrupt: too many screen-off rpm stats " + NSORPMS);
}
for (int irpm = 0; irpm < NSORPMS; irpm++) {
if (in.readInt() != 0) {
@@ -17611,6 +17715,13 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ /**
+ * Persists the monotonic clock associated with battery stats.
+ */
+ public void commitMonotonicClock() {
+ mMonotonicClock.write();
+ }
+
@GuardedBy("this")
public void prepareForDumpLocked() {
// Need to retrieve current kernel wake lock stats before printing.
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 87a3e5e14e0d..63e8d9973237 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -23,16 +23,19 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
+import android.os.Handler;
import android.os.Process;
-import android.os.UidBatteryConsumer;
import android.util.Log;
+import android.util.LogWriter;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,27 +46,45 @@ import java.util.List;
*/
public class BatteryUsageStatsProvider {
private static final String TAG = "BatteryUsageStatsProv";
+ private static final boolean DEBUG = false;
+
private final Context mContext;
private final PowerAttributor mPowerAttributor;
private final PowerStatsStore mPowerStatsStore;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
+ private final int mAccumulatedBatteryUsageStatsSpanSize;
private final Clock mClock;
+ private final MonotonicClock mMonotonicClock;
private final Object mLock = new Object();
private List<PowerCalculator> mPowerCalculators;
+ private UserPowerCalculator mUserPowerCalculator;
+ private long mLastAccumulationMonotonicHistorySize;
+
+ private static class AccumulatedBatteryUsageStats {
+ public BatteryUsageStats.Builder builder;
+ public long startWallClockTime;
+ public long startMonotonicTime;
+ public long endMonotonicTime;
+ }
public BatteryUsageStatsProvider(@NonNull Context context,
@NonNull PowerAttributor powerAttributor,
@NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies,
- @NonNull PowerStatsStore powerStatsStore, @NonNull Clock clock) {
+ @NonNull PowerStatsStore powerStatsStore, int accumulatedBatteryUsageStatsSpanSize,
+ @NonNull Clock clock, @NonNull MonotonicClock monotonicClock) {
mContext = context;
mPowerAttributor = powerAttributor;
mPowerStatsStore = powerStatsStore;
mPowerProfile = powerProfile;
mCpuScalingPolicies = cpuScalingPolicies;
+ mAccumulatedBatteryUsageStatsSpanSize = accumulatedBatteryUsageStatsSpanSize;
mClock = clock;
+ mMonotonicClock = monotonicClock;
+ mUserPowerCalculator = new UserPowerCalculator();
mPowerStatsStore.addSectionReader(new BatteryUsageStatsSection.Reader());
+ mPowerStatsStore.addSectionReader(new AccumulatedBatteryUsageStatsSection.Reader());
}
private List<PowerCalculator> getPowerCalculators() {
@@ -72,14 +93,23 @@ public class BatteryUsageStatsProvider {
mPowerCalculators = new ArrayList<>();
// Power calculators are applied in the order of registration
- mPowerCalculators.add(new BatteryChargeCalculator());
+ if (!mPowerAttributor.isPowerComponentSupported(
+ BatteryConsumer.POWER_COMPONENT_BASE)) {
+ mPowerCalculators.add(new BatteryChargeCalculator());
+ }
if (!mPowerAttributor.isPowerComponentSupported(
BatteryConsumer.POWER_COMPONENT_CPU)) {
mPowerCalculators.add(
new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
}
- mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
+ if (!mPowerAttributor.isPowerComponentSupported(
+ BatteryConsumer.POWER_COMPONENT_MEMORY)) {
+ mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
+ }
+ if (!mPowerAttributor.isPowerComponentSupported(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK)) {
+ mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
+ }
if (!BatteryStats.checkWifiOnly(mContext)) {
if (!mPowerAttributor.isPowerComponentSupported(
BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) {
@@ -131,13 +161,15 @@ public class BatteryUsageStatsProvider {
BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) {
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
}
- mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+ // IDLE power attribution is covered by WakelockPowerStatsProcessor
+ if (!mPowerAttributor.isPowerComponentSupported(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK)) {
+ mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+ }
if (!mPowerAttributor.isPowerComponentSupported(
BatteryConsumer.POWER_COMPONENT_ANY)) {
mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
}
- mPowerCalculators.add(new UserPowerCalculator());
-
if (!com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) {
// It is important that SystemServicePowerCalculator be applied last,
// because it re-attributes some of the power estimated by the other
@@ -151,6 +183,48 @@ public class BatteryUsageStatsProvider {
}
/**
+ * Conditionally runs a battery usage stats accumulation on the supplied handler.
+ */
+ public void accumulateBatteryUsageStatsAsync(BatteryStatsImpl stats, Handler handler) {
+ synchronized (this) {
+ long historySize = stats.getHistory().getMonotonicHistorySize();
+ if (historySize - mLastAccumulationMonotonicHistorySize
+ < mAccumulatedBatteryUsageStatsSpanSize) {
+ return;
+ }
+ mLastAccumulationMonotonicHistorySize = historySize;
+ }
+
+ handler.post(() -> accumulateBatteryUsageStats(stats));
+ }
+
+ /**
+ * Computes BatteryUsageStats for the period since the last accumulated stats were stored,
+ * adds them to the accumulated stats and saves the result.
+ */
+ public void accumulateBatteryUsageStats(BatteryStatsImpl stats) {
+ AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+
+ final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includePowerStateData()
+ .includeScreenStateData()
+ .build();
+ updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+
+ PowerStatsSpan powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
+ powerStatsSpan.addSection(
+ new AccumulatedBatteryUsageStatsSection(accumulatedStats.builder));
+ powerStatsSpan.addTimeFrame(accumulatedStats.startMonotonicTime,
+ accumulatedStats.startWallClockTime,
+ accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime);
+ mMonotonicClock.write();
+ mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
+ accumulatedStats.builder::discard);
+ }
+
+ /**
* Returns true if the last update was too long ago for the tolerances specified
* by the supplied queries.
*/
@@ -192,20 +266,90 @@ public class BatteryUsageStatsProvider {
private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long currentTimeMs) {
- if (query.getToTimestamp() == 0) {
- return getCurrentBatteryUsageStats(stats, query, currentTimeMs);
+ BatteryUsageStats batteryUsageStats;
+ if ((query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
+ batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+ } else if (query.getAggregatedToTimestamp() == 0) {
+ BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
+ query.getMonotonicStartTime(),
+ query.getMonotonicEndTime(), currentTimeMs);
+ batteryUsageStats = builder.build();
} else {
- return getAggregatedBatteryUsageStats(stats, query);
+ batteryUsageStats = getAggregatedBatteryUsageStats(stats, query);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "query = " + query);
+ PrintWriter pw = new PrintWriter(new LogWriter(Log.DEBUG, TAG));
+ batteryUsageStats.dump(pw, "");
+ pw.flush();
}
+ return batteryUsageStats;
}
- private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats,
+ private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long currentTimeMs) {
- final long realtimeUs = mClock.elapsedRealtime() * 1000;
- final long uptimeUs = mClock.uptimeMillis() * 1000;
+ AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+ updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+ return accumulatedStats.builder.build();
+ }
- final boolean includePowerModels = (query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ private AccumulatedBatteryUsageStats loadAccumulatedBatteryUsageStats() {
+ AccumulatedBatteryUsageStats stats = new AccumulatedBatteryUsageStats();
+ stats.startWallClockTime = 0;
+ stats.startMonotonicTime = MonotonicClock.UNDEFINED;
+ stats.endMonotonicTime = MonotonicClock.UNDEFINED;
+ PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ AccumulatedBatteryUsageStatsSection.ID,
+ AccumulatedBatteryUsageStatsSection.TYPE);
+ if (powerStatsSpan != null) {
+ List<PowerStatsSpan.Section> sections = powerStatsSpan.getSections();
+ for (int i = sections.size() - 1; i >= 0; i--) {
+ PowerStatsSpan.Section section = sections.get(i);
+ if (AccumulatedBatteryUsageStatsSection.TYPE.equals(section.getType())) {
+ stats.builder = ((AccumulatedBatteryUsageStatsSection) section)
+ .getBatteryUsageStatsBuilder();
+ stats.startWallClockTime = powerStatsSpan.getMetadata().getStartTime();
+ stats.startMonotonicTime =
+ powerStatsSpan.getMetadata().getStartMonotonicTime();
+ stats.endMonotonicTime = powerStatsSpan.getMetadata().getEndMonotonicTime();
+ break;
+ }
+ }
+ }
+ return stats;
+ }
+
+ private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats,
+ BatteryStatsImpl stats, BatteryUsageStatsQuery query) {
+ long startMonotonicTime = accumulatedStats.endMonotonicTime;
+ if (startMonotonicTime == MonotonicClock.UNDEFINED) {
+ startMonotonicTime = stats.getMonotonicStartTime();
+ }
+ long endWallClockTime = mClock.currentTimeMillis();
+ long endMonotonicTime = mMonotonicClock.monotonicTime();
+
+ if (accumulatedStats.builder == null) {
+ accumulatedStats.builder = new BatteryUsageStats.Builder(
+ stats.getCustomEnergyConsumerNames(), true, true, true, 0);
+ accumulatedStats.startWallClockTime = stats.getStartClockTime();
+ accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
+ }
+
+ accumulatedStats.endMonotonicTime = endMonotonicTime;
+
+ accumulatedStats.builder.setStatsEndTimestamp(endWallClockTime);
+ accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime);
+
+ mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(),
+ startMonotonicTime, MonotonicClock.UNDEFINED);
+
+ populateGeneralInfo(accumulatedStats.builder, stats);
+ }
+
+ private BatteryUsageStats.Builder computeBatteryUsageStats(BatteryStatsImpl stats,
+ BatteryUsageStatsQuery query, long monotonicStartTime, long monotonicEndTime,
+ long currentTimeMs) {
final boolean includeProcessStateData = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
&& stats.isProcessStateDataAvailable();
@@ -213,58 +357,70 @@ public class BatteryUsageStatsProvider {
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
- final BatteryUsageStats.Builder batteryUsageStatsBuilder;
- long monotonicStartTime, monotonicEndTime;
+ String[] customEnergyConsumerNames;
synchronized (stats) {
- monotonicStartTime = stats.getMonotonicStartTime();
- monotonicEndTime = stats.getMonotonicEndTime();
-
- batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
- stats.getCustomEnergyConsumerNames(), includePowerModels,
- includeProcessStateData, query.isScreenStateDataNeeded(),
- query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
-
- // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
- // of batteryUsageStats sessions to wall-clock adjustments
- batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime());
- batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs);
- SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats();
- for (int i = uidStats.size() - 1; i >= 0; i--) {
- final BatteryStats.Uid uid = uidStats.valueAt(i);
- if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) {
- continue;
- }
+ customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
+ }
- batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid)
- .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND,
- getProcessBackgroundTimeMs(uid, realtimeUs))
- .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND,
- getProcessForegroundTimeMs(uid, realtimeUs))
- .setTimeInProcessStateMs(
- UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
- getProcessForegroundServiceTimeMs(uid, realtimeUs));
- }
+ final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
+ customEnergyConsumerNames, includeProcessStateData, query.isScreenStateDataNeeded(),
+ query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
- final int[] powerComponents = query.getPowerComponents();
+ synchronized (stats) {
final List<PowerCalculator> powerCalculators = getPowerCalculators();
- for (int i = 0, count = powerCalculators.size(); i < count; i++) {
- PowerCalculator powerCalculator = powerCalculators.get(i);
- if (powerComponents != null) {
- boolean include = false;
- for (int powerComponent : powerComponents) {
- if (powerCalculator.isPowerComponentSupported(powerComponent)) {
- include = true;
- break;
- }
- }
- if (!include) {
+ boolean usePowerCalculators = !powerCalculators.isEmpty();
+ if (usePowerCalculators
+ && (monotonicStartTime != MonotonicClock.UNDEFINED
+ || monotonicEndTime != MonotonicClock.UNDEFINED)) {
+ Slog.wtfStack(TAG, "BatteryUsageStatsQuery specifies a time "
+ + "range that is incompatible with PowerCalculators: "
+ + powerCalculators);
+ usePowerCalculators = false;
+ }
+
+ if (monotonicStartTime == MonotonicClock.UNDEFINED) {
+ monotonicStartTime = stats.getMonotonicStartTime();
+ }
+ batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()
+ + (monotonicStartTime - stats.getMonotonicStartTime()));
+ if (monotonicEndTime != MonotonicClock.UNDEFINED) {
+ batteryUsageStatsBuilder.setStatsEndTimestamp(stats.getStartClockTime()
+ + (monotonicEndTime - stats.getMonotonicStartTime()));
+ } else {
+ batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs);
+ }
+
+ if (usePowerCalculators) {
+ final long realtimeUs = mClock.elapsedRealtime() * 1000;
+ final long uptimeUs = mClock.uptimeMillis() * 1000;
+ final int[] powerComponents = query.getPowerComponents();
+ SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats();
+ for (int i = uidStats.size() - 1; i >= 0; i--) {
+ final BatteryStats.Uid uid = uidStats.valueAt(i);
+ if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) {
continue;
}
+
+ batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
+ }
+ for (int i = 0, count = powerCalculators.size(); i < count; i++) {
+ PowerCalculator powerCalculator = powerCalculators.get(i);
+ if (powerComponents != null) {
+ boolean include = false;
+ for (int powerComponent : powerComponents) {
+ if (powerCalculator.isPowerComponentSupported(powerComponent)) {
+ include = true;
+ break;
+ }
+ }
+ if (!include) {
+ continue;
+ }
+ }
+ powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs,
+ query);
}
- powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs,
- query);
}
-
if ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory());
@@ -274,92 +430,29 @@ public class BatteryUsageStatsProvider {
mPowerAttributor.estimatePowerConsumption(batteryUsageStatsBuilder, stats.getHistory(),
monotonicStartTime, monotonicEndTime);
- BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build();
- if (includeProcessStateData) {
- verify(batteryUsageStats);
- }
- return batteryUsageStats;
- }
-
- // STOPSHIP(b/229906525): remove verification before shipping
- private static boolean sErrorReported;
+ // Combine apps by the user if necessary
+ mUserPowerCalculator.calculate(batteryUsageStatsBuilder, stats, 0, 0, query);
- private void verify(BatteryUsageStats stats) {
- if (sErrorReported) {
- return;
- }
-
- final double precision = 2.0; // Allow rounding errors up to 2 mAh
- final int[] components =
- {BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- BatteryConsumer.POWER_COMPONENT_WIFI,
- BatteryConsumer.POWER_COMPONENT_BLUETOOTH};
- final int[] states =
- {BatteryConsumer.PROCESS_STATE_FOREGROUND,
- BatteryConsumer.PROCESS_STATE_BACKGROUND,
- BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
- BatteryConsumer.PROCESS_STATE_CACHED};
- for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) {
- for (int component : components) {
- double consumedPower = ubc.getConsumedPower(ubc.getKey(component));
- double sumStates = 0;
- for (int state : states) {
- sumStates += ubc.getConsumedPower(ubc.getKey(component, state));
- }
- if (sumStates > consumedPower + precision) {
- String error = "Sum of states exceeds total. UID = " + ubc.getUid() + " "
- + BatteryConsumer.powerComponentIdToString(component)
- + " total = " + consumedPower + " states = " + sumStates;
- if (!sErrorReported) {
- Slog.wtf(TAG, error);
- sErrorReported = true;
- } else {
- Slog.e(TAG, error);
- }
- return;
- }
- }
- }
+ populateGeneralInfo(batteryUsageStatsBuilder, stats);
+ return batteryUsageStatsBuilder;
}
- private long getProcessForegroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
- final long topStateDurationUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP,
- realtimeUs, BatteryStats.STATS_SINCE_CHARGED);
- long foregroundActivityDurationUs = 0;
- final BatteryStats.Timer foregroundActivityTimer = uid.getForegroundActivityTimer();
- if (foregroundActivityTimer != null) {
- foregroundActivityDurationUs = foregroundActivityTimer.getTotalTimeLocked(realtimeUs,
- BatteryStats.STATS_SINCE_CHARGED);
+ private void populateGeneralInfo(BatteryUsageStats.Builder builder, BatteryStatsImpl stats) {
+ builder.setBatteryCapacity(stats.getEstimatedBatteryCapacity());
+ final long batteryTimeRemainingMs = stats.computeBatteryTimeRemaining(
+ mClock.elapsedRealtime() * 1000);
+ if (batteryTimeRemainingMs != -1) {
+ builder.setBatteryTimeRemainingMs(batteryTimeRemainingMs / 1000);
+ }
+ final long chargeTimeRemainingMs = stats.computeChargeTimeRemaining(
+ mClock.elapsedRealtime() * 1000);
+ if (chargeTimeRemainingMs != -1) {
+ builder.setChargeTimeRemainingMs(chargeTimeRemainingMs / 1000);
}
-
- // Use the min value of STATE_TOP time and foreground activity time, since both of these
- // times are imprecise
- long totalForegroundDurationUs = Math.min(topStateDurationUs, foregroundActivityDurationUs);
-
- totalForegroundDurationUs += uid.getProcessStateTime(
- BatteryStats.Uid.PROCESS_STATE_FOREGROUND, realtimeUs,
- BatteryStats.STATS_SINCE_CHARGED);
-
- return totalForegroundDurationUs / 1000;
- }
-
- private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
- return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
- realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
- / 1000;
- }
-
- private long getProcessForegroundServiceTimeMs(BatteryStats.Uid uid, long realtimeUs) {
- return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
- realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
- / 1000;
}
private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query) {
- final boolean includePowerModels = (query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
&& stats.isProcessStateDataAvailable();
@@ -367,7 +460,7 @@ public class BatteryUsageStatsProvider {
final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+ customEnergyConsumerNames, includeProcessStateData,
query.isScreenStateDataNeeded(), query.isPowerStateDataNeeded(),
minConsumedPowerThreshold);
if (mPowerStatsStore == null) {
@@ -401,35 +494,38 @@ public class BatteryUsageStatsProvider {
// Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
// while the "to" timestamp is *inclusive*.
boolean isInRange =
- (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
- && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+ (query.getAggregatedFromTimestamp() == 0
+ || minTime > query.getAggregatedFromTimestamp())
+ && (query.getAggregatedToTimestamp() == 0
+ || maxTime <= query.getAggregatedToTimestamp());
if (!isInRange) {
continue;
}
- PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
- spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
- if (powerStatsSpan == null) {
- continue;
- }
-
- for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
- BatteryUsageStats snapshot =
- ((BatteryUsageStatsSection) section).getBatteryUsageStats();
- if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
- customEnergyConsumerNames)) {
- Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
- + "custom power components: "
- + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ try (PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ spanMetadata.getId(), BatteryUsageStatsSection.TYPE)) {
+ if (powerStatsSpan == null) {
continue;
}
- if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
- Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
- + " does not include process state data");
- continue;
+ for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+ BatteryUsageStats snapshot =
+ ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+ if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
+ customEnergyConsumerNames)) {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+ + "custom power components: "
+ + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ continue;
+ }
+
+ if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which "
+ + " does not include process state data");
+ continue;
+ }
+ builder.add(snapshot);
}
- builder.add(snapshot);
}
}
return builder.build();
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
index af3652475376..eb896e9d4eee 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -18,6 +18,7 @@ package com.android.server.power.stats;
import android.os.BatteryUsageStats;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -28,6 +29,7 @@ import java.io.IOException;
class BatteryUsageStatsSection extends PowerStatsSpan.Section {
public static final String TYPE = "battery-usage-stats";
+ private static final String TAG = "BatteryUsageStatsSection";
private final BatteryUsageStats mBatteryUsageStats;
@@ -50,6 +52,15 @@ class BatteryUsageStatsSection extends PowerStatsSpan.Section {
mBatteryUsageStats.dump(ipw, "");
}
+ @Override
+ public void close() {
+ try {
+ mBatteryUsageStats.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Closing BatteryUsageStats", e);
+ }
+ }
+
static class Reader implements PowerStatsSpan.SectionReader {
@Override
public String getType() {
diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java
index 539c41537f8e..d7aa9876fe0d 100644
--- a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java
@@ -30,6 +30,7 @@ import com.android.internal.os.Clock;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.BluetoothPowerStatsLayout;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -142,6 +143,7 @@ public class BluetoothPowerStatsCollector extends PowerStatsCollector {
return null;
}
+ Arrays.fill(mDeviceStats, 0);
mPowerStats.uidStats.clear();
collectBluetoothActivityInfo();
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 128f14a31898..dd6484dad812 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -34,6 +34,7 @@ import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.CpuPowerStatsLayout;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Locale;
/**
@@ -330,7 +331,9 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
return null;
}
+ Arrays.fill(mCpuPowerStats.stats, 0);
mCpuPowerStats.uidStats.clear();
+
// TODO(b/305120724): additionally retrieve time-in-cluster for each CPU cluster
long newTimestampNanos = mKernelCpuStatsReader.readCpuStats(this::processUidStats,
mLayout.getScalingStepToPowerBracketMap(), mLastUpdateTimestampNanos,
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
index 1d2e38849708..079fc3c026f9 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -24,6 +24,8 @@ import com.android.internal.os.Clock;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
+import java.util.Arrays;
+
public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
public interface Injector {
@@ -105,6 +107,7 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
return null;
}
+ Arrays.fill(mPowerStats.stats, 0);
mPowerStats.uidStats.clear();
if (!mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout)) {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
index fc0611f7fcff..4f560cf68f8e 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -25,6 +25,7 @@ import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.MonotonicClock;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -50,7 +51,7 @@ import java.util.Set;
/**
* Contains power stats of various kinds, aggregated over a time span.
*/
-public class PowerStatsSpan {
+public class PowerStatsSpan implements AutoCloseable {
private static final String TAG = "PowerStatsStore";
/**
@@ -147,6 +148,40 @@ public class PowerStatsSpan {
mTimeFrames.add(timeFrame);
}
+ long getStartTime() {
+ long startTime = Long.MAX_VALUE;
+ for (int i = 0; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ if (timeFrame.startTime < startTime) {
+ startTime = timeFrame.startTime;
+ }
+ }
+ return startTime != Long.MAX_VALUE ? startTime : 0;
+ }
+
+ long getStartMonotonicTime() {
+ long startTime = Long.MAX_VALUE;
+ for (int i = 0; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ if (timeFrame.startMonotonicTime < startTime) {
+ startTime = timeFrame.startMonotonicTime;
+ }
+ }
+ return startTime != Long.MAX_VALUE ? startTime : MonotonicClock.UNDEFINED;
+ }
+
+ long getEndMonotonicTime() {
+ long maxTime = Long.MIN_VALUE;
+ for (int i = 0; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ long endTime = timeFrame.startMonotonicTime + timeFrame.duration;
+ if (endTime > maxTime) {
+ maxTime = endTime;
+ }
+ }
+ return maxTime != Long.MIN_VALUE ? maxTime : MonotonicClock.UNDEFINED;
+ }
+
void addSection(String sectionType) {
// The number of sections per span is small, so there is no need to use a Set
if (!mSections.contains(sectionType)) {
@@ -286,6 +321,13 @@ public class PowerStatsSpan {
public void dump(IndentingPrintWriter ipw) {
ipw.println(mType);
}
+
+ /**
+ * Closes the section, releasing any resources it held. Once closed, the Section
+ * should not be used.
+ */
+ public void close() {
+ }
}
/**
@@ -449,4 +491,10 @@ public class PowerStatsSpan {
ipw.decreaseIndent();
}
}
+ @Override
+ public void close() {
+ for (int i = 0; i < mSections.size(); i++) {
+ mSections.get(i).close();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
index a875c30c9ebc..d83d355fce31 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -133,10 +133,22 @@ public class PowerStatsStore {
}
/**
+ * Schedules saving the specified span on the background thread.
+ */
+ public void storePowerStatsSpanAsync(PowerStatsSpan span, Runnable onComplete) {
+ mHandler.post(() -> {
+ try {
+ storePowerStatsSpan(span);
+ } finally {
+ onComplete.run();
+ }
+ });
+ }
+
+ /**
* Saves the specified span in the store.
*/
public void storePowerStatsSpan(PowerStatsSpan span) {
- maybeClearLegacyStore();
lockStoreDirectory();
try {
if (!mStoreDir.exists()) {
@@ -172,6 +184,9 @@ public class PowerStatsStore {
lockStoreDirectory();
try {
File file = makePowerStatsSpanFilename(id);
+ if (!file.exists()) {
+ return null;
+ }
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return PowerStatsSpan.read(inputStream, parser, mSectionReaders, sectionTypes);
} catch (IOException | XmlPullParserException e) {
@@ -187,13 +202,23 @@ public class PowerStatsStore {
* Stores a {@link PowerStatsSpan} containing a single section for the supplied
* battery usage stats.
*/
- public void storeBatteryUsageStats(long monotonicStartTime,
+ public void storeBatteryUsageStatsAsync(long monotonicStartTime,
BatteryUsageStats batteryUsageStats) {
- PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
- span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
- batteryUsageStats.getStatsDuration());
- span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
- storePowerStatsSpan(span);
+ mHandler.post(() -> {
+ try {
+ PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+ span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+ batteryUsageStats.getStatsDuration());
+ span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+ storePowerStatsSpan(span);
+ } finally {
+ try {
+ batteryUsageStats.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot close BatteryUsageStats", e);
+ }
+ }
+ });
}
/**
@@ -306,9 +331,10 @@ public class PowerStatsStore {
ipw.increaseIndent();
List<PowerStatsSpan.Metadata> contents = getTableOfContents();
for (PowerStatsSpan.Metadata metadata : contents) {
- PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
- if (span != null) {
- span.dump(ipw);
+ try (PowerStatsSpan span = loadPowerStatsSpan(metadata.getId())) {
+ if (span != null) {
+ span.dump(ipw);
+ }
}
}
ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
index 8371e6681747..c38904fe2873 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
@@ -27,6 +27,8 @@ import com.android.internal.os.Clock;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.ScreenPowerStatsLayout;
+import java.util.Arrays;
+
public class ScreenPowerStatsCollector extends PowerStatsCollector {
private static final String TAG = "ScreenPowerStatsCollector";
@@ -115,6 +117,9 @@ public class ScreenPowerStatsCollector extends PowerStatsCollector {
return null;
}
+ Arrays.fill(mPowerStats.stats, 0);
+ mPowerStats.uidStats.clear();
+
mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout);
for (int display = 0; display < mDisplayCount; display++) {
@@ -142,8 +147,6 @@ public class ScreenPowerStatsCollector extends PowerStatsCollector {
mLastDozeTime[display] = screenDozeTimeMs;
}
- mPowerStats.uidStats.clear();
-
mScreenUsageTimeRetriever.retrieveTopActivityTimes((uid, topActivityTimeMs) -> {
long topActivityDuration = topActivityTimeMs - mLastTopActivityTime.get(uid);
if (topActivityDuration == 0) {
diff --git a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
new file mode 100644
index 000000000000..e3e4e1b28f43
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import android.annotation.Nullable;
+import android.os.BatteryConsumer;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.util.SparseLongArray;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.WakelockPowerStatsLayout;
+
+import java.util.Arrays;
+
+class WakelockPowerStatsCollector extends PowerStatsCollector {
+
+ public interface WakelockDurationRetriever {
+ interface Callback {
+ void onUidWakelockDuration(int uid, long wakelockDurationMs);
+ }
+
+ long getWakelockDurationMillis();
+ void retrieveUidWakelockDuration(Callback callback);
+ }
+
+ public interface Injector {
+ Handler getHandler();
+ Clock getClock();
+ PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
+ WakelockDurationRetriever getWakelockDurationRetriever();
+ }
+
+ private final WakelockDurationRetriever mWakelockDurationRetriever;
+ private WakelockPowerStatsLayout mStatsLayout;
+ private PowerStats.Descriptor mDescriptor;
+ private PowerStats mPowerStats;
+ private boolean mIsInitialized;
+ private boolean mFirstCollection = true;
+ private long mLastCollectionTime;
+ private long mLastWakelockDurationMs;
+ private final SparseLongArray mLastUidWakelockDurations = new SparseLongArray();
+
+ WakelockPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK)),
+ injector.getUidResolver(), injector.getClock());
+ mWakelockDurationRetriever = injector.getWakelockDurationRetriever();
+ }
+
+ private boolean ensureInitialized() {
+ if (mIsInitialized) {
+ return true;
+ }
+
+ if (!isEnabled()) {
+ return false;
+ }
+
+ mStatsLayout = new WakelockPowerStatsLayout();
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+ mStatsLayout.getDeviceStatsArrayLength(), null, 0,
+ mStatsLayout.getUidStatsArrayLength(), extras);
+ mPowerStats = new PowerStats(mDescriptor);
+ mIsInitialized = true;
+ return true;
+ }
+
+ @Nullable
+ @Override
+ protected PowerStats collectStats() {
+ if (!ensureInitialized()) {
+ return null;
+ }
+
+ Arrays.fill(mPowerStats.stats, 0);
+ mPowerStats.uidStats.clear();
+
+ long elapsedRealtime = mClock.elapsedRealtime();
+ mPowerStats.durationMs = elapsedRealtime - mLastCollectionTime;
+
+ long wakelockDurationMillis = mWakelockDurationRetriever.getWakelockDurationMillis();
+
+ if (!mFirstCollection) {
+ mStatsLayout.setUsageDuration(mPowerStats.stats,
+ Math.max(0, wakelockDurationMillis - mLastWakelockDurationMs));
+ }
+
+ mLastWakelockDurationMs = wakelockDurationMillis;
+
+ mWakelockDurationRetriever.retrieveUidWakelockDuration((uid, durationMs) -> {
+ if (!mFirstCollection) {
+ long diffMs = Math.max(0, durationMs - mLastUidWakelockDurations.get(uid));
+ if (diffMs != 0) {
+ long[] uidStats = mPowerStats.uidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new long[mDescriptor.uidStatsArrayLength];
+ mPowerStats.uidStats.put(uid, uidStats);
+ }
+
+ mStatsLayout.setUidUsageDuration(uidStats, diffMs);
+ }
+ }
+ mLastUidWakelockDurations.put(uid, durationMs);
+ });
+
+ mLastCollectionTime = elapsedRealtime;
+ mFirstCollection = false;
+
+ return mPowerStats;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
new file mode 100644
index 000000000000..f387feca05f2
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** A class to initialise and log metrics pulled by statsd. */
+public class WakelockStatsFrameworkEvents {
+ // statsd has a dimensional limit on the number of different keys it can handle.
+ // Beyond that limit, statsd will drop data.
+ //
+ // When we have seem SUMMARY_THRESHOLD distinct (uid, tag, wakeLockLevel) keys,
+ // we start summarizing new keys as (uid, OVERFLOW_TAG, OVERFLOW_LEVEL) to
+ // reduce the number of keys we pass to statsd.
+ //
+ // When we reach MAX_WAKELOCK_DIMENSIONS distinct keys, we summarize all new keys
+ // as (OVERFLOW_UID, HARD_CAP_TAG, OVERFLOW_LEVEL) to hard cap the number of
+ // distinct keys we pass to statsd.
+ @VisibleForTesting public static final int SUMMARY_THRESHOLD = 500;
+ @VisibleForTesting public static final int MAX_WAKELOCK_DIMENSIONS = 1000;
+
+ @VisibleForTesting public static final int HARD_CAP_UID = -1;
+ @VisibleForTesting public static final String OVERFLOW_TAG = "*overflow*";
+ @VisibleForTesting public static final String HARD_CAP_TAG = "*overflow hard cap*";
+ @VisibleForTesting public static final int OVERFLOW_LEVEL = 1;
+
+ private static class WakeLockKey {
+ private int uid;
+ private String tag;
+ private int powerManagerWakeLockLevel;
+ private int hashCode;
+
+ WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel) {
+ this.uid = uid;
+ this.tag = new String(tag);
+ this.powerManagerWakeLockLevel = powerManagerWakeLockLevel;
+
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ int getUid() {
+ return uid;
+ }
+
+ String getTag() {
+ return tag;
+ }
+
+ int getPowerManagerWakeLockLevel() {
+ return powerManagerWakeLockLevel;
+ }
+
+ void setOverflow() {
+ tag = OVERFLOW_TAG;
+ powerManagerWakeLockLevel = OVERFLOW_LEVEL;
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ void setHardCap() {
+ uid = HARD_CAP_UID;
+ tag = HARD_CAP_TAG;
+ powerManagerWakeLockLevel = OVERFLOW_LEVEL;
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof WakeLockKey)) return false;
+
+ WakeLockKey that = (WakeLockKey) o;
+ return uid == that.uid
+ && tag.equals(that.tag)
+ && powerManagerWakeLockLevel == that.powerManagerWakeLockLevel;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hashCode;
+ }
+ }
+
+ private static class WakeLockStats {
+ // accumulated uptime attributed to this WakeLock since boot, where overlap
+ // (including nesting) is ignored
+ public long uptimeMillis = 0;
+
+ // count of WakeLocks that have been acquired and then released
+ public long completedCount = 0;
+ }
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final Map<WakeLockKey, WakeLockStats> mWakeLockStats = new HashMap<>();
+
+ private static class WakeLockData {
+ // uptime millis when first acquired
+ public long acquireUptimeMillis = 0;
+ public int refCount = 0;
+
+ WakeLockData(long uptimeMillis) {
+ acquireUptimeMillis = uptimeMillis;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private final Map<WakeLockKey, WakeLockData> mOpenWakeLocks = new HashMap<>();
+
+ public void noteStartWakeLock(
+ int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
+ final WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
+
+ synchronized (mLock) {
+ WakeLockData data =
+ mOpenWakeLocks.computeIfAbsent(key, k -> new WakeLockData(eventUptimeMillis));
+ data.refCount++;
+ mOpenWakeLocks.put(key, data);
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public boolean inOverflow() {
+ return mWakeLockStats.size() >= SUMMARY_THRESHOLD;
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public boolean inHardCap() {
+ return mWakeLockStats.size() >= MAX_WAKELOCK_DIMENSIONS;
+ }
+
+ public void noteStopWakeLock(
+ int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
+ WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
+
+ synchronized (mLock) {
+ WakeLockData data = mOpenWakeLocks.get(key);
+ if (data == null) {
+ Log.e(TAG, "WakeLock not found when stopping: " + uid + " " + tag);
+ return;
+ }
+
+ if (data.refCount == 1) {
+ mOpenWakeLocks.remove(key);
+ long wakeLockDur = eventUptimeMillis - data.acquireUptimeMillis;
+
+ // Rewrite key if in an overflow state.
+ if (inOverflow() && !mWakeLockStats.containsKey(key)) {
+ key.setOverflow();
+ if (inHardCap() && !mWakeLockStats.containsKey(key)) {
+ key.setHardCap();
+ }
+ }
+
+ WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
+ stats.uptimeMillis += wakeLockDur;
+ stats.completedCount++;
+ mWakeLockStats.put(key, stats);
+ } else {
+ data.refCount--;
+ mOpenWakeLocks.put(key, data);
+ }
+ }
+ }
+
+ // Shim interface for testing.
+ @VisibleForTesting
+ public interface EventLogger {
+ void logResult(
+ int uid, String tag, int wakeLockLevel, long uptimeMillis, long completedCount);
+ }
+
+ public List<StatsEvent> pullFrameworkWakelockInfoAtoms() {
+ List<StatsEvent> result = new ArrayList<>();
+ EventLogger logger =
+ new EventLogger() {
+ public void logResult(
+ int uid,
+ String tag,
+ int wakeLockLevel,
+ long uptimeMillis,
+ long completedCount) {
+ StatsEvent event =
+ StatsEvent.newBuilder()
+ .setAtomId(FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO)
+ .writeInt(uid)
+ .writeString(tag)
+ .writeInt(wakeLockLevel)
+ .writeLong(uptimeMillis)
+ .writeLong(completedCount)
+ .build();
+ result.add(event);
+ }
+ };
+ pullFrameworkWakelockInfoAtoms(SystemClock.uptimeMillis(), logger);
+ return result;
+ }
+
+ @VisibleForTesting
+ public void pullFrameworkWakelockInfoAtoms(long nowMillis, EventLogger logger) {
+ HashSet<WakeLockKey> keys = new HashSet<>();
+
+ // Used to collect open WakeLocks when in an overflow state.
+ HashMap<WakeLockKey, WakeLockStats> openOverflowStats = new HashMap<>();
+
+ synchronized (mLock) {
+ keys.addAll(mWakeLockStats.keySet());
+
+ // If we are in an overflow state, an open wakelock may have a new key
+ // that needs to be summarized.
+ if (inOverflow()) {
+ for (WakeLockKey key : mOpenWakeLocks.keySet()) {
+ if (!mWakeLockStats.containsKey(key)) {
+ WakeLockData data = mOpenWakeLocks.get(key);
+
+ key.setOverflow();
+ if (inHardCap() && !mWakeLockStats.containsKey(key)) {
+ key.setHardCap();
+ }
+ keys.add(key);
+
+ WakeLockStats stats =
+ openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
+ stats.uptimeMillis += nowMillis - data.acquireUptimeMillis;
+ openOverflowStats.put(key, stats);
+ }
+ }
+ } else {
+ keys.addAll(mOpenWakeLocks.keySet());
+ }
+
+ for (WakeLockKey key : keys) {
+ long openWakeLockUptime = 0;
+ WakeLockData data = mOpenWakeLocks.get(key);
+ if (data != null) {
+ openWakeLockUptime = nowMillis - data.acquireUptimeMillis;
+ }
+
+ WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
+ WakeLockStats extraTime =
+ openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
+
+ stats.uptimeMillis += openWakeLockUptime + extraTime.uptimeMillis;
+
+ logger.logResult(
+ key.getUid(),
+ key.getTag(),
+ key.getPowerManagerWakeLockLevel(),
+ stats.uptimeMillis,
+ stats.completedCount);
+ }
+ }
+ }
+
+ private static final String TAG = "BatteryStatsPulledMetrics";
+
+ private final StatsPullCallbackHandler mStatsPullCallbackHandler =
+ new StatsPullCallbackHandler();
+
+ private boolean mIsInitialized = false;
+
+ public void initialize(Context context) {
+ if (mIsInitialized) {
+ return;
+ }
+
+ final StatsManager statsManager = context.getSystemService(StatsManager.class);
+ if (statsManager == null) {
+ Log.e(
+ TAG,
+ "Error retrieving StatsManager. Cannot initialize BatteryStatsPulledMetrics.");
+ } else {
+ Log.d(TAG, "Registering callback with StatsManager");
+
+ // DIRECT_EXECUTOR means that callback will run on binder thread.
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO,
+ null /* metadata */,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ mStatsPullCallbackHandler);
+ mIsInitialized = true;
+ }
+ }
+
+ private class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
+ @Override
+ public int onPullAtom(int atomTag, List<StatsEvent> data) {
+ // handle the tags appropriately.
+ List<StatsEvent> events = pullEvents(atomTag);
+ if (events == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ data.addAll(events);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private List<StatsEvent> pullEvents(int atomTag) {
+ switch (atomTag) {
+ case FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO:
+ return pullFrameworkWakelockInfoAtoms();
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 7a84b05823a4..1fdeac9816d0 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -30,6 +30,7 @@ import com.android.internal.os.Clock;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.WifiPowerStatsLayout;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -151,6 +152,9 @@ public class WifiPowerStatsCollector extends PowerStatsCollector {
return null;
}
+ Arrays.fill(mDeviceStats, 0);
+ mPowerStats.uidStats.clear();
+
WifiActivityEnergyInfo activityInfo = null;
if (mPowerReportingSupported) {
activityInfo = collectWifiActivityInfo();
@@ -224,8 +228,6 @@ public class WifiPowerStatsCollector extends PowerStatsCollector {
}
private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() {
- mPowerStats.uidStats.clear();
-
NetworkStats networkStats = mNetworkStatsSupplier.get();
if (networkStats == null) {
return null;
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 05d29f50085c..5e048810cc97 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -29,6 +29,7 @@ flag {
namespace: "backstage_power"
description: "Feature flag for streamlined connectivity battery stats"
bug: "323970018"
+ is_exported: true
}
flag {
@@ -76,3 +77,13 @@ flag {
bug: "364350206"
is_fixed_read_only: true
}
+
+flag {
+ name: "accumulate_battery_usage_stats"
+ namespace: "backstage_power"
+ description: "Add support for monotonically accumulated BatteryUsageStats"
+ bug: "345022340"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/format/BasePowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/BasePowerStatsLayout.java
new file mode 100644
index 000000000000..a9c856cfdf0b
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/format/BasePowerStatsLayout.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats.format;
+
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+
+import com.android.internal.os.PowerStats;
+
+public class BasePowerStatsLayout extends PowerStatsLayout {
+ private static final String EXTRA_DEVICE_BATTERY_DISCHARGE = "d-bd";
+ private static final String EXTRA_DEVICE_BATTERY_DISCHARGE_PCT = "d-bdp";
+ private static final String EXTRA_DEVICE_BATTERY_DISCHARGE_DURATION = "d-bdd";
+ private final int mDeviceBatteryDischargePosition;
+ private final int mDeviceBatteryDischargePercentPosition;
+ private final int mDeviceBatteryDischargeDurationPosition;
+
+ public BasePowerStatsLayout() {
+ addDeviceSectionUsageDuration();
+ addUidSectionUsageDuration();
+ mDeviceBatteryDischargePosition = addDeviceSection(1, "discharge");
+ // Stored with a 1000000 multiplier for precision
+ mDeviceBatteryDischargePercentPosition = addDeviceSection(1, "discharge-pct",
+ FLAG_FORMAT_AS_POWER);
+ mDeviceBatteryDischargeDurationPosition = addDeviceSection(1, "discharge-duration");
+ }
+
+ public BasePowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+ super(descriptor);
+ PersistableBundle extras = descriptor.extras;
+ mDeviceBatteryDischargePosition = extras.getInt(EXTRA_DEVICE_BATTERY_DISCHARGE);
+ mDeviceBatteryDischargePercentPosition = extras.getInt(EXTRA_DEVICE_BATTERY_DISCHARGE_PCT);
+ mDeviceBatteryDischargeDurationPosition =
+ extras.getInt(EXTRA_DEVICE_BATTERY_DISCHARGE_DURATION);
+ }
+
+ /**
+ * Copies the elements of the stats array layout into <code>extras</code>
+ */
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+ extras.putInt(EXTRA_DEVICE_BATTERY_DISCHARGE, mDeviceBatteryDischargePosition);
+ extras.putInt(EXTRA_DEVICE_BATTERY_DISCHARGE_PCT, mDeviceBatteryDischargePercentPosition);
+ extras.putInt(EXTRA_DEVICE_BATTERY_DISCHARGE_DURATION,
+ mDeviceBatteryDischargeDurationPosition);
+ }
+
+ /**
+ * Accumulates battery discharge amount.
+ */
+ public void addBatteryDischargeUah(long[] stats, long dischargeUah) {
+ stats[mDeviceBatteryDischargePosition] += dischargeUah;
+ }
+
+ /**
+ * Returns accumulated battery discharge amount.
+ */
+ public long getBatteryDischargeUah(long[] stats) {
+ return stats[mDeviceBatteryDischargePosition];
+ }
+
+ /**
+ * Accumulates battery discharge in percentage points.
+ */
+ public void addBatteryDischargePercent(long[] stats, int dischargePct) {
+ // store pct * 1000000 for better rounding precision
+ stats[mDeviceBatteryDischargePercentPosition] += dischargePct * 1000000L;
+ }
+
+ /**
+ * Returns battery discharge amount as percentage of battery capacity. May exceed 100% if
+ * the battery was recharged/discharged during the power stats collection session.
+ */
+ public double getBatteryDischargePercent(long[] stats) {
+ return (int) stats[mDeviceBatteryDischargePercentPosition] / 1000000.0;
+ }
+
+ /**
+ * Accumulates battery discharge duration.
+ */
+ public void addBatteryDischargeDuration(long[] stats, long durationMs) {
+ stats[mDeviceBatteryDischargeDurationPosition] += durationMs;
+ }
+
+ /**
+ * Returns accumulated battery discharge duration.
+ */
+ public long getBatteryDischargeDuration(long[] stats) {
+ return (int) stats[mDeviceBatteryDischargeDurationPosition];
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
index 4a26d83175fa..657701be60cc 100644
--- a/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
@@ -21,7 +21,9 @@ import com.android.internal.os.PowerStats;
public class BinaryStatePowerStatsLayout extends EnergyConsumerPowerStatsLayout {
public BinaryStatePowerStatsLayout() {
addDeviceSectionUsageDuration();
+ addDeviceSectionPowerEstimate();
addUidSectionUsageDuration();
+ addUidSectionPowerEstimate();
}
public BinaryStatePowerStatsLayout(PowerStats.Descriptor descriptor) {
diff --git a/services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java
index d070919f4c18..26c8ed18e667 100644
--- a/services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java
@@ -181,6 +181,9 @@ public class PowerStatsLayout {
* Extracts the usage duration from the corresponding <code>stats</code> element.
*/
public long getUsageDuration(long[] stats) {
+ if (mDeviceDurationPosition == UNSUPPORTED) {
+ return 0;
+ }
return stats[mDeviceDurationPosition];
}
@@ -234,6 +237,9 @@ public class PowerStatsLayout {
* Extracts the power estimate from a device stats array and converts it to mAh.
*/
public double getDevicePowerEstimate(long[] stats) {
+ if (mDevicePowerEstimatePosition == UNSUPPORTED) {
+ return 0;
+ }
return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
}
@@ -255,7 +261,7 @@ public class PowerStatsLayout {
* Returns true if power for this component is attributed to UIDs (apps).
*/
public boolean isUidPowerAttributionSupported() {
- return mUidPowerEstimatePosition != UNSUPPORTED;
+ return mUidPowerEstimatePosition != UNSUPPORTED || mUidDurationPosition != UNSUPPORTED;
}
/**
@@ -269,6 +275,9 @@ public class PowerStatsLayout {
* Extracts the usage duration from a UID stats array.
*/
public long getUidUsageDuration(long[] stats) {
+ if (mUidDurationPosition == UNSUPPORTED) {
+ return 0;
+ }
return stats[mUidDurationPosition];
}
@@ -314,6 +323,9 @@ public class PowerStatsLayout {
* Extracts the power estimate from a UID stats array and converts it to mAh.
*/
public double getUidPowerEstimate(long[] stats) {
+ if (mUidPowerEstimatePosition == UNSUPPORTED) {
+ return 0;
+ }
return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
}
diff --git a/services/core/java/com/android/server/power/stats/format/WakelockPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/WakelockPowerStatsLayout.java
new file mode 100644
index 000000000000..c11cf5685f2d
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/format/WakelockPowerStatsLayout.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats.format;
+
+import com.android.internal.os.PowerStats;
+
+public class WakelockPowerStatsLayout extends PowerStatsLayout {
+ public WakelockPowerStatsLayout() {
+ addDeviceSectionUsageDuration();
+ addDeviceSectionPowerEstimate();
+ addUidSectionUsageDuration();
+ addUidSectionPowerEstimate();
+ }
+
+ public WakelockPowerStatsLayout(PowerStats.Descriptor descriptor) {
+ super(descriptor);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
index a4758dd78914..a783d543559f 100644
--- a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
@@ -224,6 +224,13 @@ class AggregatedPowerStats {
}
}
+ public void noteBatteryLevel(int batteryLevel, int batteryChargeUah, long timestampMs) {
+ for (int i = 0; i < mPowerComponentStats.size(); i++) {
+ mPowerComponentStats.valueAt(i).noteBatteryLevel(batteryLevel, batteryChargeUah,
+ timestampMs);
+ }
+ }
+
void finish(long timestampMs) {
for (int i = 0; i < mPowerComponentStats.size(); i++) {
PowerComponentAggregatedPowerStats component = mPowerComponentStats.valueAt(i);
diff --git a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java
index eaeda43ef6af..945960148770 100644
--- a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java
@@ -174,36 +174,36 @@ class AggregatedPowerStatsConfig {
* standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
* a custom power component.
*/
- PowerComponent trackPowerComponent(int powerComponentId) {
+ PowerComponent trackPowerComponent(@BatteryConsumer.PowerComponentId int powerComponentId) {
PowerComponent builder = new PowerComponent(powerComponentId);
mPowerComponents.add(builder);
return builder;
}
/**
- * Creates a configuration for the specified power component, which is a subcomponent
- * of a different power component. The tracked states will be the same as the parent
- * component's.
+ * Creates a configuration for the specified power component, whose attribution calculation
+ * depends on a different power component. The tracked states will be the same as the
+ * "dependsOn" component's.
*/
- PowerComponent trackPowerComponent(int powerComponentId,
- int parentPowerComponentId) {
- PowerComponent parent = null;
+ PowerComponent trackPowerComponent(@BatteryConsumer.PowerComponentId int powerComponentId,
+ @BatteryConsumer.PowerComponentId int dependsOnPowerComponentId) {
+ PowerComponent dependsOnPowerComponent = null;
for (int i = 0; i < mPowerComponents.size(); i++) {
PowerComponent powerComponent = mPowerComponents.get(i);
- if (powerComponent.getPowerComponentId() == parentPowerComponentId) {
- parent = powerComponent;
+ if (powerComponent.getPowerComponentId() == dependsOnPowerComponentId) {
+ dependsOnPowerComponent = powerComponent;
break;
}
}
- if (parent == null) {
+ if (dependsOnPowerComponent == null) {
throw new IllegalArgumentException(
- "Parent component " + parentPowerComponentId + " is not configured");
+ "Required component " + dependsOnPowerComponentId + " is not configured");
}
PowerComponent powerComponent = trackPowerComponent(powerComponentId);
- powerComponent.mTrackedDeviceStates = parent.mTrackedDeviceStates;
- powerComponent.mTrackedUidStates = parent.mTrackedUidStates;
+ powerComponent.mTrackedDeviceStates = dependsOnPowerComponent.mTrackedDeviceStates;
+ powerComponent.mTrackedUidStates = dependsOnPowerComponent.mTrackedUidStates;
return powerComponent;
}
diff --git a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
index 32dfdf915bca..5f93bdf07f47 100644
--- a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
@@ -50,12 +50,14 @@ class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor {
return;
}
- if (mScreenPowerStatsDescriptor == null) {
- mScreenPowerStatsDescriptor = screenStats.getPowerStatsDescriptor();
- if (mScreenPowerStatsDescriptor == null) {
- return;
- }
+ PowerStats.Descriptor screenDescriptor = screenStats.getPowerStatsDescriptor();
+ if (screenDescriptor == null) {
+ return;
+ }
+ if (mScreenPowerStatsDescriptor == null
+ || !mScreenPowerStatsDescriptor.equals(screenDescriptor)) {
+ mScreenPowerStatsDescriptor = screenDescriptor;
mScreenPowerStatsLayout = new ScreenPowerStatsLayout(mScreenPowerStatsDescriptor);
mTmpScreenStats = new long[mScreenPowerStatsDescriptor.statsArrayLength];
}
diff --git a/services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java
index ad1b4a7cc487..f5d10fdd2dd2 100644
--- a/services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java
@@ -20,12 +20,10 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.PowerProfile;
-import com.android.server.power.stats.PowerStatsUidResolver;
class AudioPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- AudioPowerStatsProcessor(PowerProfile powerProfile,
- PowerStatsUidResolver uidResolver) {
- super(BatteryConsumer.POWER_COMPONENT_AUDIO, uidResolver,
+ AudioPowerStatsProcessor(PowerProfile powerProfile) {
+ super(BatteryConsumer.POWER_COMPONENT_AUDIO,
powerProfile.getAveragePower(PowerProfile.POWER_AUDIO));
}
diff --git a/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java
new file mode 100644
index 000000000000..1459ff55aceb
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats.processor;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.SparseLongArray;
+
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.BasePowerStatsLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.DoubleSupplier;
+
+class BasePowerStatsProcessor extends PowerStatsProcessor {
+ private final DoubleSupplier mBatteryCapacitySupplier;
+ private PowerEstimationPlan mPlan;
+ private long mStartTimestamp;
+ private final SparseLongArray mUidStartTimestamps = new SparseLongArray();
+ private static final BasePowerStatsLayout sStatsLayout = new BasePowerStatsLayout();
+ private final PowerStats.Descriptor mPowerStatsDescriptor;
+ private final long[] mTmpUidStatsArray;
+ private double mBatteryCapacityUah;
+ private int mBatteryLevel;
+ private int mBatteryChargeUah;
+ private long mBatteryLevelTimestampMs;
+ private int mCumulativeDischargePct;
+ private long mCumulativeDischargeUah;
+ private long mCumulativeDischargeDurationMs;
+
+ private static final int UNSPECIFIED = -1;
+
+ BasePowerStatsProcessor(DoubleSupplier batteryCapacitySupplier) {
+ mBatteryCapacitySupplier = batteryCapacitySupplier;
+ PersistableBundle extras = new PersistableBundle();
+ sStatsLayout.toExtras(extras);
+ mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_BASE,
+ sStatsLayout.getDeviceStatsArrayLength(), null, 0,
+ sStatsLayout.getUidStatsArrayLength(), extras);
+ mTmpUidStatsArray = new long[sStatsLayout.getUidStatsArrayLength()];
+ }
+
+ @Override
+ void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ mStartTimestamp = timestampMs;
+ mUidStartTimestamps.clear();
+ stats.setPowerStatsDescriptor(mPowerStatsDescriptor);
+ mBatteryCapacityUah = mBatteryCapacitySupplier.getAsDouble() * 1000;
+ mBatteryLevel = UNSPECIFIED;
+ mBatteryChargeUah = UNSPECIFIED;
+ mBatteryLevelTimestampMs = UNSPECIFIED;
+ mCumulativeDischargeUah = 0;
+ mCumulativeDischargePct = 0;
+ mCumulativeDischargeDurationMs = 0;
+ }
+
+ @Override
+ public void noteBatteryLevel(int batteryLevel, int batteryChargeUah, long timestampMs) {
+ boolean discharging = false;
+ if (mBatteryLevel != UNSPECIFIED && batteryLevel < mBatteryLevel) {
+ mCumulativeDischargePct += mBatteryLevel - batteryLevel;
+ discharging = true;
+ }
+
+ if (mBatteryChargeUah != UNSPECIFIED && batteryChargeUah != 0
+ && batteryChargeUah < mBatteryChargeUah) {
+ mCumulativeDischargeUah += mBatteryChargeUah - batteryChargeUah;
+ discharging = true;
+ }
+
+ if (discharging) {
+ if (mBatteryLevelTimestampMs != UNSPECIFIED) {
+ mCumulativeDischargeDurationMs += timestampMs - mBatteryLevelTimestampMs;
+ }
+ }
+
+ mBatteryLevel = batteryLevel;
+ mBatteryChargeUah = batteryChargeUah;
+ mBatteryLevelTimestampMs = timestampMs;
+ }
+
+ @Override
+ public void setUidState(PowerComponentAggregatedPowerStats stats, int uid,
+ @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long timestampMs) {
+ super.setUidState(stats, uid, stateId, state, timestampMs);
+ if (stateId == STATE_PROCESS_STATE && mUidStartTimestamps.indexOfKey(uid) < 0) {
+ mUidStartTimestamps.put(uid, timestampMs);
+ }
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ PowerStats powerStats = new PowerStats(mPowerStatsDescriptor);
+ sStatsLayout.setUsageDuration(powerStats.stats, timestampMs - mStartTimestamp);
+
+ sStatsLayout.addBatteryDischargePercent(powerStats.stats, mCumulativeDischargePct);
+ if (mCumulativeDischargeUah != 0) {
+ sStatsLayout.addBatteryDischargeUah(powerStats.stats,
+ mCumulativeDischargeUah);
+ } else {
+ sStatsLayout.addBatteryDischargeUah(powerStats.stats,
+ (long) (mCumulativeDischargePct * mBatteryCapacityUah / 100.0));
+ }
+ sStatsLayout.addBatteryDischargeDuration(powerStats.stats, mCumulativeDischargeDurationMs);
+ mCumulativeDischargePct = 0;
+ mCumulativeDischargeUah = 0;
+ mCumulativeDischargeDurationMs = 0;
+
+ List<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+
+ if (!uids.isEmpty()) {
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ Integer uid = uids.get(i);
+ long durationMs = timestampMs - mUidStartTimestamps.get(uid, mStartTimestamp);
+ mUidStartTimestamps.put(uid, timestampMs);
+
+ long[] uidStats = new long[sStatsLayout.getUidStatsArrayLength()];
+ sStatsLayout.setUidUsageDuration(uidStats, durationMs);
+ powerStats.uidStats.put(uid, uidStats);
+ }
+ }
+
+ stats.addPowerStats(powerStats, timestampMs);
+
+ for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+ int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length];
+ uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_UNSPECIFIED;
+
+ for (int j = uids.size() - 1; j >= 0; j--) {
+ int uid = uids.get(j);
+ int[] stateValues = uidStateEstimate.combinedDeviceStateEstimate.stateValues;
+ uidStateValues[STATE_SCREEN] = stateValues[STATE_SCREEN];
+ uidStateValues[STATE_POWER] = stateValues[STATE_POWER];
+ // Erase usage duration for UNSPECIFIED proc state - the app was not running
+ if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) {
+ if (sStatsLayout.getUidUsageDuration(mTmpUidStatsArray) != 0) {
+ sStatsLayout.setUidUsageDuration(mTmpUidStatsArray, 0);
+ stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray);
+ }
+ }
+ }
+ }
+
+ mStartTimestamp = timestampMs;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
index e45a707bdc8d..9fe7f3e7a542 100644
--- a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
@@ -22,7 +22,6 @@ import android.os.PersistableBundle;
import android.os.Process;
import com.android.internal.os.PowerStats;
-import com.android.server.power.stats.PowerStatsUidResolver;
import com.android.server.power.stats.UsageBasedPowerEstimator;
import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
@@ -45,7 +44,6 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
}
private final int mPowerComponentId;
- private final PowerStatsUidResolver mUidResolver;
private final UsageBasedPowerEstimator mUsageBasedPowerEstimator;
private boolean mEnergyConsumerSupported;
private int mInitiatingUid = Process.INVALID_UID;
@@ -60,18 +58,14 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
private long[] mTmpDeviceStatsArray;
private long[] mTmpUidStatsArray;
- BinaryStatePowerStatsProcessor(int powerComponentId,
- PowerStatsUidResolver uidResolver, double averagePowerMilliAmp) {
- this(powerComponentId, uidResolver, averagePowerMilliAmp,
- new BinaryStatePowerStatsLayout());
+ BinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp) {
+ this(powerComponentId, averagePowerMilliAmp, new BinaryStatePowerStatsLayout());
}
- BinaryStatePowerStatsProcessor(int powerComponentId,
- PowerStatsUidResolver uidResolver, double averagePowerMilliAmp,
+ BinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp,
BinaryStatePowerStatsLayout statsLayout) {
mPowerComponentId = powerComponentId;
mUsageBasedPowerEstimator = new UsageBasedPowerEstimator(averagePowerMilliAmp);
- mUidResolver = uidResolver;
mStatsLayout = statsLayout;
}
@@ -115,13 +109,13 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
if (state == STATE_ON) {
if (item.eventCode == (BatteryStats.HistoryItem.EVENT_STATE_CHANGE
| BatteryStats.HistoryItem.EVENT_FLAG_START)) {
- mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
+ mInitiatingUid = item.eventTag.uid;
}
} else {
if (mInitiatingUid == Process.INVALID_UID) {
if (item.eventCode == (BatteryStats.HistoryItem.EVENT_STATE_CHANGE
| BatteryStats.HistoryItem.EVENT_FLAG_FINISH)) {
- mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
+ mInitiatingUid = item.eventTag.uid;
}
}
recordUsageDuration(mPowerStats, mInitiatingUid, item.time);
diff --git a/services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java
index 830906167ee2..46e060011d92 100644
--- a/services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java
@@ -20,12 +20,10 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.PowerProfile;
-import com.android.server.power.stats.PowerStatsUidResolver;
class CameraPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- CameraPowerStatsProcessor(PowerProfile powerProfile,
- PowerStatsUidResolver uidResolver) {
- super(BatteryConsumer.POWER_COMPONENT_CAMERA, uidResolver,
+ CameraPowerStatsProcessor(PowerProfile powerProfile) {
+ super(BatteryConsumer.POWER_COMPONENT_CAMERA,
powerProfile.getAveragePower(PowerProfile.POWER_CAMERA));
}
diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
index 5f7a3dad99e8..17ceca6e3dc1 100644
--- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
@@ -16,6 +16,8 @@
package com.android.server.power.stats.processor;
+import android.annotation.Nullable;
+import android.os.BatteryConsumer;
import android.util.ArraySet;
import android.util.Log;
@@ -23,6 +25,7 @@ import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.CpuPowerStatsLayout;
+import com.android.server.power.stats.format.WakelockPowerStatsLayout;
import java.util.ArrayList;
import java.util.Arrays;
@@ -73,6 +76,11 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
// Temp array for retrieval of UID power stats, to avoid repeated allocations
private long[] mTmpUidStatsArray;
+ private PowerStats.Descriptor mWakelockDescriptor;
+ private WakelockPowerStatsLayout mWakelockPowerStatsLayout;
+ private long[] mTmpWakelockDeviceStats;
+ private long[] mTmpWakelockUidStats;
+
CpuPowerStatsProcessor(PowerProfile powerProfile, CpuScalingPolicies scalingPolicies) {
mCpuScalingPolicies = scalingPolicies;
mCpuScalingStepCount = scalingPolicies.getScalingStepCount();
@@ -109,6 +117,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+
+ mWakelockDescriptor = null;
}
/**
@@ -174,7 +184,9 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
}
estimatePowerByScalingStep(intermediates);
- estimatePowerByDeviceState(stats, intermediates);
+
+ PowerComponentAggregatedPowerStats wakelockStats = getWakelockStats(stats);
+ estimatePowerByDeviceState(stats, intermediates, wakelockStats);
combineDeviceStateEstimates();
ArrayList<Integer> uids = new ArrayList<>();
@@ -182,13 +194,45 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
if (!uids.isEmpty()) {
for (int uid : uids) {
for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i));
+ estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i),
+ wakelockStats);
}
}
}
mPlan.resetIntermediates();
}
+ @Nullable
+ private PowerComponentAggregatedPowerStats getWakelockStats(
+ PowerComponentAggregatedPowerStats stats) {
+ // Wakelock power estimates are only relevant when the CPU power estimates are derived from
+ // EnergyConsumers.
+ if (mStatsLayout.getEnergyConsumerCount() == 0) {
+ return null;
+ }
+
+ PowerComponentAggregatedPowerStats wakelockStats =
+ stats.getAggregatedPowerStats().getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK);
+ if (wakelockStats == null) {
+ return null;
+ }
+
+ PowerStats.Descriptor descriptor = wakelockStats.getPowerStatsDescriptor();
+ if (descriptor == null) {
+ return null;
+ }
+
+ if (mWakelockDescriptor == null || !mWakelockDescriptor.equals(descriptor)) {
+ mWakelockDescriptor = descriptor;
+ mWakelockPowerStatsLayout = new WakelockPowerStatsLayout(mWakelockDescriptor);
+ mTmpWakelockDeviceStats = new long[mWakelockDescriptor.statsArrayLength];
+ mTmpWakelockUidStats = new long[mWakelockDescriptor.uidStatsArrayLength];
+ }
+
+ return wakelockStats;
+ }
+
/*
* Populate data structures (two maps) needed to use power rail data, aka energy consumers,
* to attribute power usage to apps.
@@ -345,7 +389,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
}
private void estimatePowerByDeviceState(PowerComponentAggregatedPowerStats stats,
- Intermediates intermediates) {
+ Intermediates intermediates,
+ @Nullable PowerComponentAggregatedPowerStats wakelockStats) {
int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap();
@@ -382,6 +427,15 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
for (int i = deviceStatsIntermediates.powerByBracket.length - 1; i >= 0; i--) {
power += deviceStatsIntermediates.powerByBracket[i];
}
+
+ if (wakelockStats != null) {
+ wakelockStats.getDeviceStats(mTmpWakelockDeviceStats,
+ deviceStateEstimation.stateValues);
+ double wakelockPowerEstimate = mWakelockPowerStatsLayout.getDevicePowerEstimate(
+ mTmpWakelockDeviceStats);
+ power = Math.max(0, power - wakelockPowerEstimate);
+ }
+
deviceStatsIntermediates.power = power;
mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray);
@@ -454,7 +508,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
}
private void estimateUidPowerConsumption(PowerComponentAggregatedPowerStats stats, int uid,
- UidStateEstimate uidStateEstimate) {
+ UidStateEstimate uidStateEstimate,
+ @Nullable PowerComponentAggregatedPowerStats wakelockStats) {
CombinedDeviceStateEstimate combinedDeviceStateEstimate =
uidStateEstimate.combinedDeviceStateEstimate;
DeviceStatsIntermediates cdsIntermediates =
@@ -482,6 +537,14 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
/ cdsIntermediates.timeByBracket[bracket];
}
+ if (wakelockStats != null) {
+ wakelockStats.getUidStats(mTmpWakelockUidStats, uid,
+ proportionalEstimate.stateValues);
+ double wakelockPowerEstimate = mWakelockPowerStatsLayout.getUidPowerEstimate(
+ mTmpWakelockUidStats);
+ power = Math.max(0, power - wakelockPowerEstimate);
+ }
+
mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
}
diff --git a/services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java
index b0bef69dfc49..e9c1b8253123 100644
--- a/services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java
@@ -20,12 +20,10 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.PowerProfile;
-import com.android.server.power.stats.PowerStatsUidResolver;
class FlashlightPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- FlashlightPowerStatsProcessor(PowerProfile powerProfile,
- PowerStatsUidResolver uidResolver) {
- super(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, uidResolver,
+ FlashlightPowerStatsProcessor(PowerProfile powerProfile) {
+ super(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
powerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT));
}
diff --git a/services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java
index f1e3e90e7099..2476432dc504 100644
--- a/services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java
@@ -23,7 +23,6 @@ import android.os.Process;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
-import com.android.server.power.stats.PowerStatsUidResolver;
import com.android.server.power.stats.UsageBasedPowerEstimator;
import com.android.server.power.stats.format.GnssPowerStatsLayout;
@@ -40,10 +39,9 @@ class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
private final long[] mGnssSignalDurations =
new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
- GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
- super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
- powerProfile.getAveragePower(PowerProfile.POWER_GPS_ON),
- sStatsLayout);
+ GnssPowerStatsProcessor(PowerProfile powerProfile) {
+ super(BatteryConsumer.POWER_COMPONENT_GNSS,
+ powerProfile.getAveragePower(PowerProfile.POWER_GPS_ON), sStatsLayout);
boolean useSignalLevelEstimators = false;
for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java b/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java
index 2ba4a5254c5a..877a06fb8c4f 100644
--- a/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java
+++ b/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java
@@ -32,9 +32,9 @@ import com.android.internal.os.PowerProfile;
import com.android.server.power.stats.PowerAttributor;
import com.android.server.power.stats.PowerStatsSpan;
import com.android.server.power.stats.PowerStatsStore;
-import com.android.server.power.stats.PowerStatsUidResolver;
import java.util.List;
+import java.util.function.DoubleSupplier;
public class MultiStatePowerAttributor implements PowerAttributor {
private static final String TAG = "MultiStatePowerAttributor";
@@ -44,14 +44,12 @@ public class MultiStatePowerAttributor implements PowerAttributor {
private final PowerStatsAggregator mPowerStatsAggregator;
private final SparseBooleanArray mPowerStatsExporterEnabled = new SparseBooleanArray();
- // TODO(b/346371828): remove dependency on PowerStatsUidResolver. At the time of power
- // attribution isolates UIDs are supposed to be long forgotten.
public MultiStatePowerAttributor(Context context, PowerStatsStore powerStatsStore,
@NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies,
- @NonNull PowerStatsUidResolver powerStatsUidResolver) {
+ @NonNull DoubleSupplier batteryCapacitySupplier) {
this(powerStatsStore, new PowerStatsAggregator(
createAggregatedPowerStatsConfig(context, powerProfile, cpuScalingPolicies,
- powerStatsUidResolver)));
+ batteryCapacitySupplier)));
}
@VisibleForTesting
@@ -62,13 +60,14 @@ public class MultiStatePowerAttributor implements PowerAttributor {
mPowerStatsStore.addSectionReader(
new AggregatedPowerStatsSection.Reader(mPowerStatsAggregator.getConfig()));
mPowerStatsExporter = new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator);
+ setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_BASE, true);
}
private static AggregatedPowerStatsConfig createAggregatedPowerStatsConfig(Context context,
PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies,
- PowerStatsUidResolver powerStatsUidResolver) {
+ DoubleSupplier batteryCapacitySupplier) {
AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
- config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_BASE)
.trackDeviceStates(
AggregatedPowerStatsConfig.STATE_POWER,
AggregatedPowerStatsConfig.STATE_SCREEN)
@@ -76,6 +75,21 @@ public class MultiStatePowerAttributor implements PowerAttributor {
AggregatedPowerStatsConfig.STATE_POWER,
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessorSupplier(() -> new BasePowerStatsProcessor(batteryCapacitySupplier));
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WAKELOCK)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessorSupplier(
+ () -> new WakelockPowerStatsProcessor(powerProfile));
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK)
.setProcessorSupplier(
() -> new CpuPowerStatsProcessor(powerProfile, cpuScalingPolicies));
@@ -139,7 +153,7 @@ public class MultiStatePowerAttributor implements PowerAttributor {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessorSupplier(
- () -> new AudioPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+ () -> new AudioPowerStatsProcessor(powerProfile));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_VIDEO)
.trackDeviceStates(
@@ -150,7 +164,7 @@ public class MultiStatePowerAttributor implements PowerAttributor {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessorSupplier(
- () -> new VideoPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+ () -> new VideoPowerStatsProcessor(powerProfile));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)
.trackDeviceStates(
@@ -161,8 +175,7 @@ public class MultiStatePowerAttributor implements PowerAttributor {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessorSupplier(
- () -> new FlashlightPowerStatsProcessor(powerProfile,
- powerStatsUidResolver));
+ () -> new FlashlightPowerStatsProcessor(powerProfile));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
.trackDeviceStates(
@@ -173,7 +186,7 @@ public class MultiStatePowerAttributor implements PowerAttributor {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessorSupplier(
- () -> new CameraPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+ () -> new CameraPowerStatsProcessor(powerProfile));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
.trackDeviceStates(
@@ -184,7 +197,7 @@ public class MultiStatePowerAttributor implements PowerAttributor {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessorSupplier(
- () -> new GnssPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+ () -> new GnssPowerStatsProcessor(powerProfile));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
.trackDeviceStates(
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
index d04c5baf921f..d4f8fd92fc6c 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
@@ -125,6 +125,10 @@ class PowerComponentAggregatedPowerStats {
mProcessor.noteStateChange(this, item);
}
+ public void noteBatteryLevel(int batteryLevel, int batteryChargeUah, long timestampMs) {
+ mProcessor.noteBatteryLevel(batteryLevel, batteryChargeUah, timestampMs);
+ }
+
void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long timestampMs) {
if (mDeviceStats == null) {
@@ -163,6 +167,11 @@ class PowerComponentAggregatedPowerStats {
void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long timestampMs) {
+ mProcessor.setUidState(this, uid, stateId, state, timestampMs);
+ }
+
+ void setProcessedUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId,
+ int state, long timestampMs) {
if (!mUidStateConfig[stateId].isTracked()) {
return;
}
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
index 32c1056908d5..dcdd3bd8b3fa 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
@@ -53,7 +53,11 @@ public class PowerStatsAggregator {
return mAggregatedPowerStatsConfig;
}
- void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
+ /**
+ * Marks the power component as enabled for PowerStats aggregation
+ */
+ @VisibleForTesting
+ public void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
synchronized (this) {
if (mStats != null) {
mStats = null;
@@ -81,23 +85,23 @@ public class PowerStatsAggregator {
mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig, mEnabledComponents);
}
- mStats.start(startTimeMs);
-
- boolean clockUpdateAdded = false;
+ boolean startedSession = false;
long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED;
long lastTime = 0;
int lastStates = 0xFFFFFFFF;
int lastStates2 = 0xFFFFFFFF;
+ int lastBatteryLevel = 0;
try (BatteryStatsHistoryIterator iterator = history.iterate(startTimeMs, endTimeMs)) {
while (iterator.hasNext()) {
BatteryStats.HistoryItem item = iterator.next();
- if (!clockUpdateAdded) {
+ if (!startedSession) {
+ mStats.start(item.time);
mStats.addClockUpdate(item.time, item.currentTime);
if (baseTime == UNINITIALIZED) {
baseTime = item.time;
}
- clockUpdateAdded = true;
+ startedSession = true;
} else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
|| item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
mStats.addClockUpdate(item.time, item.currentTime);
@@ -105,6 +109,12 @@ public class PowerStatsAggregator {
lastTime = item.time;
+ if (item.batteryLevel != lastBatteryLevel) {
+ mStats.noteBatteryLevel(item.batteryLevel, item.batteryChargeUah,
+ item.time);
+ lastBatteryLevel = item.batteryLevel;
+ }
+
int batteryState =
(item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
? AggregatedPowerStatsConfig.POWER_STATE_OTHER
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index fab87d6684e1..ef0e63bece90 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -28,6 +28,7 @@ import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.PowerStatsSpan;
import com.android.server.power.stats.PowerStatsStore;
+import com.android.server.power.stats.format.BasePowerStatsLayout;
import com.android.server.power.stats.format.PowerStatsLayout;
import java.util.ArrayList;
@@ -45,6 +46,13 @@ class PowerStatsExporter {
private final PowerStatsAggregator mPowerStatsAggregator;
private final long mBatterySessionTimeSpanSlackMillis;
private static final long BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS = TimeUnit.MINUTES.toMillis(2);
+ private static final BasePowerStatsLayout sBasePowerStatsLayout = new BasePowerStatsLayout();
+
+ private static class BatteryLevelInfo {
+ public double batteryDischargePct;
+ public double batteryDischargeMah;
+ public long batteryDischargeDurationMs;
+ }
PowerStatsExporter(PowerStatsStore powerStatsStore, PowerStatsAggregator powerStatsAggregator) {
this(powerStatsStore, powerStatsAggregator, BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS);
@@ -97,18 +105,19 @@ class PowerStatsExporter {
maxEndTime = spanMaxTime;
}
- PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
- AggregatedPowerStatsSection.TYPE);
- if (span == null) {
- Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
- continue;
- }
- List<PowerStatsSpan.Section> sections = span.getSections();
- for (int k = 0; k < sections.size(); k++) {
- hasStoredSpans = true;
- PowerStatsSpan.Section section = sections.get(k);
- populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
- ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
+ try (PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ AggregatedPowerStatsSection.TYPE)) {
+ if (span == null) {
+ Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
+ continue;
+ }
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ for (int k = 0; k < sections.size(); k++) {
+ hasStoredSpans = true;
+ PowerStatsSpan.Section section = sections.get(k);
+ populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
+ ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
+ }
}
}
@@ -145,6 +154,8 @@ class PowerStatsExporter {
PowerStatsLayout layout = new PowerStatsLayout(descriptor);
long[] deviceStats = new long[descriptor.statsArrayLength];
+ BatteryLevelInfo batteryLevelInfo = new BatteryLevelInfo();
+
for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
@@ -159,12 +170,12 @@ class PowerStatsExporter {
if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
populateAggregatedBatteryConsumer(batteryUsageStatsBuilder,
powerComponentStats,
- layout, deviceStats, screenState, powerState);
+ layout, deviceStats, batteryLevelInfo, screenState, powerState);
}
} else if (powerState == BatteryConsumer.POWER_STATE_BATTERY) {
populateAggregatedBatteryConsumer(batteryUsageStatsBuilder,
powerComponentStats,
- layout, deviceStats, screenState, powerState);
+ layout, deviceStats, batteryLevelInfo, screenState, powerState);
}
}
}
@@ -172,15 +183,22 @@ class PowerStatsExporter {
populateBatteryConsumers(batteryUsageStatsBuilder,
powerComponentStats, layout);
}
+
+ populateBatteryLevelInfo(batteryUsageStatsBuilder, batteryLevelInfo);
}
private void populateAggregatedBatteryConsumer(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
- long[] deviceStats, @BatteryConsumer.ScreenState int screenState,
+ long[] deviceStats, BatteryLevelInfo batteryLevelInfo,
+ @BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState) {
int powerComponentId = powerComponentStats.powerComponentId;
+ boolean hasBatteryLevelProperties = powerComponentId == BatteryConsumer.POWER_COMPONENT_BASE
+ && powerState == BatteryConsumer.POWER_STATE_BATTERY;
+
double[] totalPower = new double[1];
+ long[] durationMs = new long[1];
MultiStateStats.States.forEachTrackedStateCombination(
powerComponentStats.getConfig().getDeviceStateConfig(),
states -> {
@@ -193,6 +211,11 @@ class PowerStatsExporter {
}
totalPower[0] += layout.getDevicePowerEstimate(deviceStats);
+ durationMs[0] += layout.getUsageDuration(deviceStats);
+
+ if (hasBatteryLevelProperties) {
+ gatherBatteryLevelInfo(batteryLevelInfo, deviceStats);
+ }
});
AggregateBatteryConsumer.Builder deviceScope =
@@ -201,11 +224,34 @@ class PowerStatsExporter {
BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, deviceScope,
powerComponentId, screenState, powerState);
if (key != null) {
- deviceScope.addConsumedPower(key, totalPower[0],
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ deviceScope.addConsumedPower(key, totalPower[0]);
+ deviceScope.addUsageDurationMillis(key, durationMs[0]);
+ }
+ key = deviceScope.getKey(powerComponentId, BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
+ if (key != null) {
+ deviceScope.addConsumedPower(key, totalPower[0]);
+ deviceScope.addUsageDurationMillis(key, durationMs[0]);
}
- deviceScope.addConsumedPower(powerComponentId, totalPower[0],
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
+
+ private void gatherBatteryLevelInfo(BatteryLevelInfo batteryLevelInfo, long[] deviceStats) {
+ batteryLevelInfo.batteryDischargePct +=
+ sBasePowerStatsLayout.getBatteryDischargePercent(deviceStats);
+ batteryLevelInfo.batteryDischargeMah +=
+ sBasePowerStatsLayout.getBatteryDischargeUah(deviceStats) / 1000.0;
+ batteryLevelInfo.batteryDischargeDurationMs +=
+ sBasePowerStatsLayout.getBatteryDischargeDuration(deviceStats);
+ }
+
+ private void populateBatteryLevelInfo(BatteryUsageStats.Builder builder,
+ BatteryLevelInfo batteryLevelInfo) {
+ builder.setDischargePercentage((int) Math.round(batteryLevelInfo.batteryDischargePct))
+ .setDischargedPowerRange(batteryLevelInfo.batteryDischargeMah,
+ batteryLevelInfo.batteryDischargeMah)
+ .setDischargeDurationMs(batteryLevelInfo.batteryDischargeDurationMs)
+ .getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(batteryLevelInfo.batteryDischargeMah);
}
private void populateBatteryConsumers(
@@ -216,9 +262,16 @@ class PowerStatsExporter {
PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
- boolean breakDownByProcState = batteryUsageStatsBuilder.isProcessStateDataNeeded()
- && powerComponent
- .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked();
+ boolean breakDownByProcState;
+ if (powerComponent.getPowerComponentId() == BatteryConsumer.POWER_COMPONENT_BASE) {
+ breakDownByProcState = true;
+ } else if (batteryUsageStatsBuilder.isProcessStateDataNeeded()) {
+ breakDownByProcState = powerComponent
+ .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE]
+ .isTracked();
+ } else {
+ breakDownByProcState = false;
+ }
ArrayList<Integer> uids = new ArrayList<>();
powerComponentStats.collectUids(uids);
@@ -263,12 +316,15 @@ class PowerStatsExporter {
powerComponentStats.powerComponentId;
double[] powerByProcState =
new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
+ long[] durationByProcState =
+ new long[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
double powerAllApps = 0;
for (int uid : uids) {
UidBatteryConsumer.Builder builder =
batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
Arrays.fill(powerByProcState, 0);
+ Arrays.fill(durationByProcState, 0);
MultiStateStats.States.forEachTrackedStateCombination(
powerComponent.getUidStateConfig(),
@@ -282,6 +338,7 @@ class PowerStatsExporter {
}
double power = layout.getUidPowerEstimate(uidStats);
+ long duration = layout.getUidUsageDuration(uidStats);
if (breakDownByProcState) {
int procState = states[AggregatedPowerStatsConfig.STATE_PROCESS_STATE];
// There is a difference in how PowerComponentAggregatedPowerStats
@@ -296,8 +353,10 @@ class PowerStatsExporter {
procState = BatteryConsumer.PROCESS_STATE_BACKGROUND;
}
powerByProcState[procState] += power;
+ durationByProcState[procState] += duration;
}
powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED] += power;
+ durationByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED] += duration;
});
int resultScreenState = batteryUsageStatsBuilder.isScreenStateDataNeeded()
@@ -306,19 +365,28 @@ class PowerStatsExporter {
? powerState : BatteryConsumer.POWER_STATE_UNSPECIFIED;
for (int procState = 0; procState < powerByProcState.length; procState++) {
double power = powerByProcState[procState];
- if (power == 0) {
+ long duration = durationByProcState[procState];
+ if (power == 0 && duration == 0) {
continue;
}
BatteryConsumer.Key key = builder.getKey(powerComponentId, procState,
resultScreenState, resultPowerState);
- builder.addConsumedPower(key, power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+ if (key != null) {
+ builder.addConsumedPower(key, power);
+ builder.addUsageDurationMillis(key, duration);
+ }
}
if (resultScreenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED
|| resultPowerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
- builder.addConsumedPower(powerComponentId,
- powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED],
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ BatteryConsumer.Key key = builder.getKey(powerComponentId,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
+ if (key != null) {
+ builder.addConsumedPower(key,
+ powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
+ builder.addUsageDurationMillis(key,
+ durationByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
+ }
}
powerAllApps += powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED];
}
@@ -329,11 +397,9 @@ class PowerStatsExporter {
BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, allAppsScope,
powerComponentId, screenState, powerState);
if (key != null) {
- allAppsScope.addConsumedPower(key, powerAllApps,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ allAppsScope.addConsumedPower(key, powerAllApps);
}
- allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ allAppsScope.addConsumedPower(powerComponentId, powerAllApps);
}
@Nullable
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java
index 838fc628ce95..c1a5414404bb 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java
@@ -55,6 +55,14 @@ abstract class PowerStatsProcessor {
BatteryStats.HistoryItem item) {
}
+ public void noteBatteryLevel(int batteryLevel, int batteryChargeUah, long timestampMs) {
+ }
+
+ public void setUidState(PowerComponentAggregatedPowerStats stats, int uid,
+ @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long timestampMs) {
+ stats.setProcessedUidState(uid, stateId, state, timestampMs);
+ }
+
void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats,
long timestampMs) {
stats.addProcessedPowerStats(powerStats, timestampMs);
diff --git a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
index b295e309d4fb..8e7498f38fcb 100644
--- a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
@@ -89,14 +89,15 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor {
return true;
}
- mLastUsedDescriptor = descriptor;
- mStatsLayout = new ScreenPowerStatsLayout(descriptor);
- if (mStatsLayout.getDisplayCount() != mDisplayCount) {
- Slog.e(TAG, "Incompatible number of displays: " + mStatsLayout.getDisplayCount()
+ ScreenPowerStatsLayout statsLayout = new ScreenPowerStatsLayout(descriptor);
+ if (statsLayout.getDisplayCount() != mDisplayCount) {
+ Slog.e(TAG, "Incompatible number of displays: " + statsLayout.getDisplayCount()
+ ", expected: " + mDisplayCount);
return false;
}
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = statsLayout;
mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
return true;
diff --git a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
index 67013ea65aa3..0bb028bce5af 100644
--- a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
@@ -146,7 +146,10 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor {
return;
}
- if (item.eventTag == null || item.eventTag.string == null
+ if (((item.eventCode & BatteryStats.HistoryItem.EVENT_TYPE_MASK)
+ != BatteryStats.HistoryItem.EVENT_STATE_CHANGE)
+ || item.eventTag == null
+ || item.eventTag.string == null
|| !item.eventTag.string.startsWith(SENSOR_EVENT_TAG_PREFIX)) {
return;
}
diff --git a/services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java
index a6c380725fa5..5514ca7763f9 100644
--- a/services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java
@@ -20,11 +20,10 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.PowerProfile;
-import com.android.server.power.stats.PowerStatsUidResolver;
class VideoPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- VideoPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
- super(BatteryConsumer.POWER_COMPONENT_VIDEO, uidResolver,
+ VideoPowerStatsProcessor(PowerProfile powerProfile) {
+ super(BatteryConsumer.POWER_COMPONENT_VIDEO,
powerProfile.getAveragePower(PowerProfile.POWER_VIDEO));
}
diff --git a/services/core/java/com/android/server/power/stats/processor/WakelockPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/WakelockPowerStatsProcessor.java
new file mode 100644
index 000000000000..0cb4991c1673
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/processor/WakelockPowerStatsProcessor.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats.processor;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.WakelockPowerStatsLayout;
+
+class WakelockPowerStatsProcessor extends PowerStatsProcessor {
+ private static final WakelockPowerStatsLayout sStatsLayout = new WakelockPowerStatsLayout();
+ private final UsageBasedPowerEstimator mPowerEstimator;
+
+ WakelockPowerStatsProcessor(PowerProfile powerProfile) {
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE));
+ }
+
+ @Override
+ void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats,
+ long timestampMs) {
+ long durationMs = sStatsLayout.getUsageDuration(powerStats.stats);
+ if (durationMs == 0) {
+ return;
+ }
+
+ double power = mPowerEstimator.calculatePower(durationMs);
+ sStatsLayout.setDevicePowerEstimate(powerStats.stats, power);
+
+ long totalDuration = 0;
+ for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) {
+ totalDuration += sStatsLayout.getUidUsageDuration(powerStats.uidStats.valueAt(i));
+ }
+
+ if (totalDuration != 0) {
+ for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) {
+ long[] uidStats = powerStats.uidStats.valueAt(i);
+ sStatsLayout.setUidPowerEstimate(uidStats,
+ power * sStatsLayout.getUidUsageDuration(uidStats) / totalDuration);
+ }
+ }
+
+ super.addPowerStats(stats, powerStats, timestampMs);
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ // Nothing to do. Power attribution has already been done in `addPowerStats`
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index f047f564538d..ab630eef4644 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -17,6 +17,7 @@
package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_BLUETOOTH;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
@@ -63,6 +64,7 @@ public class CpuWakeupStats {
private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger";
private static final String SUBSYSTEM_SENSOR_STRING = "Sensor";
private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data";
+ private static final String SUBSYSTEM_BLUETOOTH_STRING = "Bluetooth";
private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
@@ -512,6 +514,8 @@ public class CpuWakeupStats {
return CPU_WAKEUP_SUBSYSTEM_SENSOR;
case SUBSYSTEM_CELLULAR_DATA_STRING:
return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
+ case SUBSYSTEM_BLUETOOTH_STRING:
+ return CPU_WAKEUP_SUBSYSTEM_BLUETOOTH;
}
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -528,6 +532,8 @@ public class CpuWakeupStats {
return SUBSYSTEM_SENSOR_STRING;
case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA:
return SUBSYSTEM_CELLULAR_DATA_STRING;
+ case CPU_WAKEUP_SUBSYSTEM_BLUETOOTH:
+ return SUBSYSTEM_BLUETOOTH_STRING;
case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
return "Unknown";
}
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 685ab3a9424f..ab756f2a755b 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -54,7 +54,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
-import com.android.server.RescueParty;
+import com.android.server.crashrecovery.CrashRecoveryAdaptor;
import com.android.server.pm.pkg.AndroidPackage;
import java.io.File;
@@ -627,7 +627,7 @@ class Rollback {
if (!deprecateFlagsAndSettingsResets()) {
// Clear flags.
- RescueParty.resetDeviceConfigForPackages(packageNames);
+ CrashRecoveryAdaptor.rescuePartyResetDeviceConfigForPackages(packageNames);
}
Consumer<Intent> onResult = result -> {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e753ce845ddc..1bed48a09d9e 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -163,6 +163,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
private final RollbackPackageHealthObserver mPackageHealthObserver;
private final AppDataRollbackHelper mAppDataRollbackHelper;
private final Runnable mRunExpiration = this::runExpiration;
+ private final PackageWatchdog mPackageWatchdog;
// The # of milli-seconds to sleep for each received ACTION_PACKAGE_ENABLE_ROLLBACK.
// Used by #blockRollbackManager to test timeout in enabling rollbacks.
@@ -190,6 +191,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
+ mPackageWatchdog = PackageWatchdog.getInstance(mContext);
// Kick off and start monitoring the handler thread.
HandlerThread handlerThread = new HandlerThread("RollbackManagerServiceHandler");
@@ -1249,12 +1251,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
// should document in PackageInstaller.SessionParams#setEnableRollback
// After enabling and committing any rollback, observe packages and
// prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
} else {
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
runExpiration();
}
@@ -1317,7 +1319,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
}
});
- PackageWatchdog.getInstance(mContext).dump(ipw);
+ mPackageWatchdog.dump(ipw);
}
@AnyThread
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
deleted file mode 100644
index f78c4488cbfb..000000000000
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ /dev/null
@@ -1,819 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.rollback;
-
-import static android.content.pm.Flags.provideInfoOfApkInApex;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-
-import android.annotation.AnyThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.WorkerThread;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.content.rollback.RollbackManager;
-import android.crashrecovery.flags.Flags;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.sysprop.CrashRecoveryProperties;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.PackageWatchdog;
-import com.android.server.PackageWatchdog.FailureReasons;
-import com.android.server.PackageWatchdog.PackageHealthObserver;
-import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-import com.android.server.pm.ApexManager;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * {@link PackageHealthObserver} for {@link RollbackManagerService}.
- * This class monitors crashes and triggers RollbackManager rollback accordingly.
- * It also monitors native crashes for some short while after boot.
- *
- * @hide
- */
-public final class RollbackPackageHealthObserver implements PackageHealthObserver {
- private static final String TAG = "RollbackPackageHealthObserver";
- private static final String NAME = "rollback-observer";
- private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
-
- private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
- | ApplicationInfo.FLAG_SYSTEM;
-
- private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
- "persist.device_config.configuration.disable_high_impact_rollback";
-
- private final Context mContext;
- private final Handler mHandler;
- private final ApexManager mApexManager;
- private final File mLastStagedRollbackIdsFile;
- private final File mTwoPhaseRollbackEnabledFile;
- // Staged rollback ids that have been committed but their session is not yet ready
- private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
- // True if needing to roll back only rebootless apexes when native crash happens
- private boolean mTwoPhaseRollbackEnabled;
-
- @VisibleForTesting
- public RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
- mContext = context;
- HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
- File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
- dataDir.mkdirs();
- mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
- mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
- mApexManager = apexManager;
-
- if (SystemProperties.getBoolean("sys.boot_completed", false)) {
- // Load the value from the file if system server has crashed and restarted
- mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile);
- } else {
- // Disable two-phase rollback for a normal reboot. We assume the rebootless apex
- // installed before reboot is stable if native crash didn't happen.
- mTwoPhaseRollbackEnabled = false;
- writeBoolean(mTwoPhaseRollbackEnabledFile, false);
- }
- }
-
- RollbackPackageHealthObserver(Context context) {
- this(context, ApexManager.getInstance());
- }
-
- @Override
- public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
- @FailureReasons int failureReason, int mitigationCount) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
- List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
- availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
- if (!lowImpactRollbacks.isEmpty()) {
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- // For native crashes, we will directly roll back any available rollbacks at low
- // impact level
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
- // Rollback is available for crashing low impact package
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else {
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
- }
- }
- } else {
- boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty();
-
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
- && anyRollbackAvailable) {
- // For native crashes, we will directly roll back any available rollbacks
- // Note: For non-native crashes the rollback-all step has higher impact
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getAvailableRollback(failedPackage) != null) {
- // Rollback is available, we may get a callback into #execute
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (anyRollbackAvailable) {
- // If any rollbacks are available, we will commit them
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
- }
- }
-
- Slog.i(TAG, "Checking available remediations for health check failure."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " failureReason: " + failureReason
- + " available impact: " + impact);
- return impact;
- }
-
- @Override
- public boolean execute(@Nullable VersionedPackage failedPackage,
- @FailureReasons int rollbackReason, int mitigationCount) {
- Slog.i(TAG, "Executing remediation."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " rollbackReason: " + rollbackReason
- + " mitigationCount: " + mitigationCount);
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
- return true;
- }
-
- List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
- availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
- RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
- } else if (!lowImpactRollbacks.isEmpty()) {
- // Apply all available low impact rollbacks.
- mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
- }
- } else {
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAll(rollbackReason));
- return true;
- }
-
- RollbackInfo rollback = getAvailableRollback(failedPackage);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
- } else {
- mHandler.post(() -> rollbackAll(rollbackReason));
- }
- }
-
- // Assume rollbacks executed successfully
- return true;
- }
-
- @Override
- public int onBootLoop(int mitigationCount) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
- if (!availableRollbacks.isEmpty()) {
- impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
- }
- }
- return impact;
- }
-
- @Override
- public boolean executeBootLoopMitigation(int mitigationCount) {
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
-
- triggerLeastImpactLevelRollback(availableRollbacks,
- PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
- return true;
- }
- return false;
- }
-
-
- @Override
- public String getUniqueIdentifier() {
- return NAME;
- }
-
- @Override
- public boolean isPersistent() {
- return true;
- }
-
- @Override
- public boolean mayObservePackage(String packageName) {
- if (getAvailableRollbacks().isEmpty()) {
- return false;
- }
- return isPersistentSystemApp(packageName);
- }
-
- private List<RollbackInfo> getAvailableRollbacks() {
- return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
- }
-
- private boolean isPersistentSystemApp(@NonNull String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private void assertInWorkerThread() {
- Preconditions.checkState(mHandler.getLooper().isCurrentThread());
- }
-
- /**
- * Start observing health of {@code packages} for {@code durationMs}.
- * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
- */
- @AnyThread
- void startObservingHealth(List<String> packages, long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
- }
-
- @AnyThread
- void notifyRollbackAvailable(RollbackInfo rollback) {
- mHandler.post(() -> {
- // Enable two-phase rollback when a rebootless apex rollback is made available.
- // We assume the rebootless apex is stable and is less likely to be the cause
- // if native crash doesn't happen before reboot. So we will clear the flag and disable
- // two-phase rollback after reboot.
- if (isRebootlessApex(rollback)) {
- mTwoPhaseRollbackEnabled = true;
- writeBoolean(mTwoPhaseRollbackEnabledFile, true);
- }
- });
- }
-
- private static boolean isRebootlessApex(RollbackInfo rollback) {
- if (!rollback.isStaged()) {
- for (PackageRollbackInfo info : rollback.getPackages()) {
- if (info.isApex()) {
- return true;
- }
- }
- }
- return false;
- }
-
- /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
- * to check for native crashes and mitigate them if needed.
- */
- @AnyThread
- void onBootCompletedAsync() {
- mHandler.post(()->onBootCompleted());
- }
-
- @WorkerThread
- private void onBootCompleted() {
- assertInWorkerThread();
-
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
- // TODO(gavincorkery): Call into Package Watchdog from outside the observer
- PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
- }
-
- SparseArray<String> rollbackIds = popLastStagedRollbackIds();
- for (int i = 0; i < rollbackIds.size(); i++) {
- WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext,
- rollbackIds.keyAt(i), rollbackIds.valueAt(i),
- rollbackManager.getRecentlyCommittedRollbacks());
- }
- }
-
- @AnyThread
- private RollbackInfo getAvailableRollback(VersionedPackage failedPackage) {
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
- for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
- if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
- return rollback;
- }
- // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
- // to rely on complicated reasoning as below
-
- // Due to b/147666157, for apk in apex, we do not know the version we are rolling
- // back from. But if a package X is embedded in apex A exclusively (not embedded in
- // any other apex), which is not guaranteed, then it is sufficient to check only
- // package names here, as the version of failedPackage and the PackageRollbackInfo
- // can't be different. If failedPackage has a higher version, then it must have
- // been updated somehow. There are two ways: it was updated by an update of apex A
- // or updated directly as apk. In both cases, this rollback would have gotten
- // expired when onPackageReplaced() was called. Since the rollback exists, it has
- // same version as failedPackage.
- if (packageRollback.isApkInApex()
- && packageRollback.getVersionRolledBackFrom().getPackageName()
- .equals(failedPackage.getPackageName())) {
- return rollback;
- }
- }
- }
- return null;
- }
-
- @AnyThread
- private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
- List<RollbackInfo> availableRollbacks) {
- if (failedPackage == null) {
- return null;
- }
-
- for (RollbackInfo rollback : availableRollbacks) {
- for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
- if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
- return rollback;
- }
- // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
- // to rely on complicated reasoning as below
-
- // Due to b/147666157, for apk in apex, we do not know the version we are rolling
- // back from. But if a package X is embedded in apex A exclusively (not embedded in
- // any other apex), which is not guaranteed, then it is sufficient to check only
- // package names here, as the version of failedPackage and the PackageRollbackInfo
- // can't be different. If failedPackage has a higher version, then it must have
- // been updated somehow. There are two ways: it was updated by an update of apex A
- // or updated directly as apk. In both cases, this rollback would have gotten
- // expired when onPackageReplaced() was called. Since the rollback exists, it has
- // same version as failedPackage.
- if (packageRollback.isApkInApex()
- && packageRollback.getVersionRolledBackFrom().getPackageName()
- .equals(failedPackage.getPackageName())) {
- return rollback;
- }
- }
- }
- return null;
- }
-
- /**
- * Returns {@code true} if staged session associated with {@code rollbackId} was marked
- * as handled, {@code false} if already handled.
- */
- @WorkerThread
- private boolean markStagedSessionHandled(int rollbackId) {
- assertInWorkerThread();
- return mPendingStagedRollbackIds.remove(rollbackId);
- }
-
- /**
- * Returns {@code true} if all pending staged rollback sessions were marked as handled,
- * {@code false} if there is any left.
- */
- @WorkerThread
- private boolean isPendingStagedSessionsEmpty() {
- assertInWorkerThread();
- return mPendingStagedRollbackIds.isEmpty();
- }
-
- private static boolean readBoolean(File file) {
- try (FileInputStream fis = new FileInputStream(file)) {
- return fis.read() == 1;
- } catch (IOException ignore) {
- return false;
- }
- }
-
- private static void writeBoolean(File file, boolean value) {
- try (FileOutputStream fos = new FileOutputStream(file)) {
- fos.write(value ? 1 : 0);
- fos.flush();
- FileUtils.sync(fos);
- } catch (IOException ignore) {
- }
- }
-
- @WorkerThread
- private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) {
- assertInWorkerThread();
- writeStagedRollbackId(mLastStagedRollbackIdsFile, stagedRollbackId, logPackage);
- }
-
- static void writeStagedRollbackId(File file, int stagedRollbackId,
- @Nullable VersionedPackage logPackage) {
- try {
- FileOutputStream fos = new FileOutputStream(file, true);
- PrintWriter pw = new PrintWriter(fos);
- String logPackageName = logPackage != null ? logPackage.getPackageName() : "";
- pw.append(String.valueOf(stagedRollbackId)).append(",").append(logPackageName);
- pw.println();
- pw.flush();
- FileUtils.sync(fos);
- pw.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to save last staged rollback id", e);
- file.delete();
- }
- }
-
- @WorkerThread
- private SparseArray<String> popLastStagedRollbackIds() {
- assertInWorkerThread();
- try {
- return readStagedRollbackIds(mLastStagedRollbackIdsFile);
- } finally {
- mLastStagedRollbackIdsFile.delete();
- }
- }
-
- static SparseArray<String> readStagedRollbackIds(File file) {
- SparseArray<String> result = new SparseArray<>();
- try {
- String line;
- BufferedReader reader = new BufferedReader(new FileReader(file));
- while ((line = reader.readLine()) != null) {
- // Each line is of the format: "id,logging_package"
- String[] values = line.trim().split(",");
- String rollbackId = values[0];
- String logPackageName = "";
- if (values.length > 1) {
- logPackageName = values[1];
- }
- result.put(Integer.parseInt(rollbackId), logPackageName);
- }
- } catch (Exception ignore) {
- return new SparseArray<>();
- }
- return result;
- }
-
-
- /**
- * Returns true if the package name is the name of a module.
- */
- @AnyThread
- private boolean isModule(String packageName) {
- PackageManager pm = mContext.getPackageManager();
-
- if (Flags.refactorCrashrecovery() && provideInfoOfApkInApex()) {
- // Check if the package is listed among the system modules or is an
- // APK inside an updatable APEX.
- try {
- final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
- String apexPackageName = pkg.getApexPackageName();
- if (apexPackageName != null) {
- packageName = apexPackageName;
- }
-
- return pm.getModuleInfo(packageName, 0 /* flags */) != null;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- } else {
- // Check if the package is an APK inside an APEX. If it is, use the parent APEX package
- // when querying PackageManager.
- String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
- packageName);
- if (apexPackageName != null) {
- packageName = apexPackageName;
- }
-
- try {
- return pm.getModuleInfo(packageName, 0) != null;
- } catch (PackageManager.NameNotFoundException ignore) {
- return false;
- }
- }
- }
-
- /**
- * Rolls back the session that owns {@code failedPackage}
- *
- * @param rollback {@code rollbackInfo} of the {@code failedPackage}
- * @param failedPackage the package that needs to be rolled back
- */
- @WorkerThread
- private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
- @FailureReasons int rollbackReason) {
- assertInWorkerThread();
- String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName());
-
- Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId()
- + " failedPackage: " + failedPackageName
- + " rollbackReason: " + rollbackReason);
- logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s",
- failedPackageName, rollbackReason));
- final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
- final String failedPackageToLog;
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- failedPackageToLog = SystemProperties.get(
- "sys.init.updatable_crashing_process_name", "");
- } else {
- failedPackageToLog = failedPackage.getPackageName();
- }
- VersionedPackage logPackageTemp = null;
- if (isModule(failedPackage.getPackageName())) {
- logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage);
- }
-
- final VersionedPackage logPackage = logPackageTemp;
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
- reasonToLog, failedPackageToLog);
-
- Consumer<Intent> onResult = result -> {
- assertInWorkerThread();
- int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
- RollbackManager.STATUS_FAILURE);
- if (status == RollbackManager.STATUS_SUCCESS) {
- if (rollback.isStaged()) {
- int rollbackId = rollback.getRollbackId();
- saveStagedRollbackId(rollbackId, logPackage);
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
- reasonToLog, failedPackageToLog);
-
- } else {
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
- reasonToLog, failedPackageToLog);
- }
- } else {
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
- reasonToLog, failedPackageToLog);
- }
- if (rollback.isStaged()) {
- markStagedSessionHandled(rollback.getRollbackId());
- // Wait for all pending staged sessions to get handled before rebooting.
- if (isPendingStagedSessionsEmpty()) {
- CrashRecoveryProperties.attemptingReboot(true);
- mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
- }
- }
- };
-
- if (Flags.refactorCrashrecovery()) {
- // Define a BroadcastReceiver to handle the result
- BroadcastReceiver rollbackReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent result) {
- mHandler.post(() -> onResult.accept(result));
- }
- };
-
- String intentActionName = CLASS_NAME + rollback.getRollbackId();
- // Register the BroadcastReceiver
- mContext.registerReceiver(rollbackReceiver,
- new IntentFilter(intentActionName),
- Context.RECEIVER_NOT_EXPORTED);
-
- Intent intentReceiver = new Intent(intentActionName);
- intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
- intentReceiver.setPackage(mContext.getPackageName());
- intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
- PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
- rollback.getRollbackId(),
- intentReceiver,
- PendingIntent.FLAG_MUTABLE);
-
- rollbackManager.commitRollback(rollback.getRollbackId(),
- Collections.singletonList(failedPackage),
- rollbackPendingIntent.getIntentSender());
- } else {
- final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> {
- mHandler.post(() -> onResult.accept(result));
- });
-
- rollbackManager.commitRollback(rollback.getRollbackId(),
- Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
- }
- }
-
- /**
- * Two-phase rollback:
- * 1. roll back rebootless apexes first
- * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
- *
- * This approach gives us a better chance to correctly attribute native crash to rebootless
- * apex update without rolling back Mainline updates which might contains critical security
- * fixes.
- */
- @WorkerThread
- private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) {
- assertInWorkerThread();
- if (!mTwoPhaseRollbackEnabled) {
- return false;
- }
-
- Slog.i(TAG, "Rolling back all rebootless APEX rollbacks");
- boolean found = false;
- for (RollbackInfo rollback : rollbacks) {
- if (isRebootlessApex(rollback)) {
- VersionedPackage firstRollback =
- rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, firstRollback,
- PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
- found = true;
- }
- }
- return found;
- }
-
- /**
- * Rollback the package that has minimum rollback impact level.
- * @param availableRollbacks all available rollbacks
- * @param rollbackReason reason to rollback
- */
- private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
- @FailureReasons int rollbackReason) {
- int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
-
- if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
- // Apply all available low impact rollbacks.
- mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
- } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
- // Check disable_high_impact_rollback device config before performing rollback
- if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
- return;
- }
- // Rollback one package at a time. If that doesn't resolve the issue, rollback
- // next with same impact level.
- mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
- }
- }
-
- /**
- * sort the available high impact rollbacks by first package name to have a deterministic order.
- * Apply the first available rollback.
- * @param availableRollbacks all available rollbacks
- * @param rollbackReason reason to rollback
- */
- @WorkerThread
- private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
- @FailureReasons int rollbackReason) {
- assertInWorkerThread();
- List<RollbackInfo> highImpactRollbacks =
- getRollbacksAvailableForImpactLevel(
- availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
-
- // sort rollbacks based on package name of the first package. This is to have a
- // deterministic order of rollbacks.
- List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
- Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
- VersionedPackage firstRollback =
- sortedHighImpactRollbacks
- .get(0)
- .getPackages()
- .get(0)
- .getVersionRolledBackFrom();
- Slog.i(TAG, "Rolling back high impact rollback for package: "
- + firstRollback.getPackageName());
- rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
- }
-
- @WorkerThread
- private void rollbackAll(@FailureReasons int rollbackReason) {
- assertInWorkerThread();
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
- if (useTwoPhaseRollback(rollbacks)) {
- return;
- }
-
- Slog.i(TAG, "Rolling back all available rollbacks");
- // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
- // pending staged rollbacks are handled.
- for (RollbackInfo rollback : rollbacks) {
- if (rollback.isStaged()) {
- mPendingStagedRollbackIds.add(rollback.getRollbackId());
- }
- }
-
- for (RollbackInfo rollback : rollbacks) {
- VersionedPackage firstRollback =
- rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, firstRollback, rollbackReason);
- }
- }
-
- /**
- * Rollback all available low impact rollbacks
- * @param availableRollbacks all available rollbacks
- * @param rollbackReason reason to rollbacks
- */
- @WorkerThread
- private void rollbackAllLowImpact(
- List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
- assertInWorkerThread();
-
- List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
- availableRollbacks,
- PackageManager.ROLLBACK_USER_IMPACT_LOW);
- if (useTwoPhaseRollback(lowImpactRollbacks)) {
- return;
- }
-
- Slog.i(TAG, "Rolling back all available low impact rollbacks");
- logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason);
- // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
- // pending staged rollbacks are handled.
- for (RollbackInfo rollback : lowImpactRollbacks) {
- if (rollback.isStaged()) {
- mPendingStagedRollbackIds.add(rollback.getRollbackId());
- }
- }
-
- for (RollbackInfo rollback : lowImpactRollbacks) {
- VersionedPackage firstRollback =
- rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, firstRollback, rollbackReason);
- }
- }
-
- private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
- List<RollbackInfo> availableRollbacks, int impactLevel) {
- return availableRollbacks.stream()
- .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
- .toList();
- }
-
- private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
- return availableRollbacks.stream()
- .mapToInt(RollbackInfo::getRollbackImpactLevel)
- .min()
- .orElse(-1);
- }
-
- private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- int minImpact = getMinRollbackImpactLevel(availableRollbacks);
- switch (minImpact) {
- case PackageManager.ROLLBACK_USER_IMPACT_LOW:
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
- break;
- case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
- if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
- }
- break;
- default:
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- return impact;
- }
-
- @VisibleForTesting
- Handler getHandler() {
- return mHandler;
- }
-}
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
deleted file mode 100644
index 79560ce27919..000000000000
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.rollback;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.PackageWatchdog;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.util.List;
-
-/**
- * This class handles the logic for logging Watchdog-triggered rollback events.
- */
-public final class WatchdogRollbackLogger {
- private static final String TAG = "WatchdogRollbackLogger";
-
- private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
-
- private WatchdogRollbackLogger() {
- }
-
- @Nullable
- private static String getLoggingParentName(Context context, @NonNull String packageName) {
- PackageManager packageManager = context.getPackageManager();
- try {
- int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA;
- ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo;
- if (ai.metaData == null) {
- return null;
- }
- return ai.metaData.getString(LOGGING_PARENT_KEY);
- } catch (Exception e) {
- Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e);
- return null;
- }
- }
-
- /**
- * Returns the logging parent of a given package if it exists, {@code null} otherwise.
- *
- * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the
- * metadata of a package's AndroidManifest.xml.
- */
- @VisibleForTesting
- @Nullable
- static VersionedPackage getLogPackage(Context context,
- @NonNull VersionedPackage failingPackage) {
- String logPackageName;
- VersionedPackage loggingParent;
- logPackageName = getLoggingParentName(context, failingPackage.getPackageName());
- if (logPackageName == null) {
- return null;
- }
- try {
- loggingParent = new VersionedPackage(logPackageName, context.getPackageManager()
- .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode());
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- return loggingParent;
- }
-
- static void logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName,
- List<RollbackInfo> recentlyCommittedRollbacks) {
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
-
- RollbackInfo rollback = null;
- for (RollbackInfo info : recentlyCommittedRollbacks) {
- if (rollbackId == info.getRollbackId()) {
- rollback = info;
- break;
- }
- }
-
- if (rollback == null) {
- Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId);
- return;
- }
-
- // Use the version of the logging parent that was installed before
- // we rolled back for logging purposes.
- VersionedPackage oldLoggingPackage = null;
- if (!TextUtils.isEmpty(logPackageName)) {
- for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
- if (logPackageName.equals(packageRollback.getPackageName())) {
- oldLoggingPackage = packageRollback.getVersionRolledBackFrom();
- break;
- }
- }
- }
-
- int sessionId = rollback.getCommittedSessionId();
- PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
- if (sessionInfo == null) {
- Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
- return;
- }
-
- if (sessionInfo.isStagedSessionApplied()) {
- logEvent(oldLoggingPackage,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
- } else if (sessionInfo.isStagedSessionFailed()) {
- logEvent(oldLoggingPackage,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
- }
- }
-
- /**
- * Log a Watchdog rollback event to statsd.
- *
- * @param logPackage the package to associate the rollback with.
- * @param type the state of the rollback.
- * @param rollbackReason the reason Watchdog triggered a rollback, if known.
- * @param failingPackageName the failing package or process which triggered the rollback.
- */
- public static void logEvent(@Nullable VersionedPackage logPackage, int type,
- int rollbackReason, @NonNull String failingPackageName) {
- String logMsg = "Watchdog event occurred with type: " + rollbackTypeToString(type)
- + " logPackage: " + logPackage
- + " rollbackReason: " + rollbackReasonToString(rollbackReason)
- + " failedPackageName: " + failingPackageName;
- Slog.i(TAG, logMsg);
- if (logPackage != null) {
- CrashRecoveryStatsLog.write(
- CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
- type,
- logPackage.getPackageName(),
- logPackage.getVersionCode(),
- rollbackReason,
- failingPackageName,
- new byte[]{});
- } else {
- // In the case that the log package is null, still log an empty string as an
- // indication that retrieving the logging parent failed.
- CrashRecoveryStatsLog.write(
- CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
- type,
- "",
- 0,
- rollbackReason,
- failingPackageName,
- new byte[]{});
- }
-
- logTestProperties(logMsg);
- }
-
- /**
- * Writes properties which will be used by rollback tests to check if particular rollback
- * events have occurred.
- */
- private static void logTestProperties(String logMsg) {
- // This property should be on only during the tests
- if (!SystemProperties.getBoolean("persist.sys.rollbacktest.enabled", false)) {
- return;
- }
- logCrashRecoveryEvent(Log.DEBUG, logMsg);
- }
-
- @VisibleForTesting
- static int mapFailureReasonToMetric(@PackageWatchdog.FailureReasons int failureReason) {
- switch (failureReason) {
- case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
- case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
- case PackageWatchdog.FAILURE_REASON_APP_CRASH:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
- case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
- case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
- default:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
- }
- }
-
- private static String rollbackTypeToString(int type) {
- switch (type) {
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
- return "ROLLBACK_INITIATE";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
- return "ROLLBACK_SUCCESS";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE:
- return "ROLLBACK_FAILURE";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED:
- return "ROLLBACK_BOOT_TRIGGERED";
- default:
- return "UNKNOWN";
- }
- }
-
- private static String rollbackReasonToString(int reason) {
- switch (reason) {
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
- return "REASON_NATIVE_CRASH";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
- return "REASON_EXPLICIT_HEALTH_CHECK";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
- return "REASON_APP_CRASH";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
- return "REASON_APP_NOT_RESPONDING";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
- return "REASON_NATIVE_CRASH_DURING_BOOT";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
- return "REASON_BOOT_LOOP";
- default:
- return "UNKNOWN";
- }
- }
-}
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 8f603fc34b32..075a31f3b24c 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -191,8 +191,7 @@ public class RotationResolverManagerService extends
SensorPrivacyManager.Sensors.CAMERA);
if (mIsServiceEnabled && isCameraAvailable) {
final RotationResolverManagerPerUserService service =
- getServiceForUserLocked(
- UserHandle.getCallingUserId());
+ getServiceForUserLocked(UserHandle.USER_CURRENT);
final RotationResolutionRequest request;
if (packageName == null) {
request = new RotationResolutionRequest(/* packageName */ "",
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index 55f85ea27c82..22a359bced86 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -17,10 +17,10 @@
package com.android.server.security;
import static android.Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNSUPPORTED_PROFILE;
import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE;
import static android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -88,8 +88,8 @@ public class AttestationVerificationManagerService extends SystemService {
public void verifyToken(VerificationToken token, ParcelDuration parcelDuration,
AndroidFuture resultCallback) throws RemoteException {
enforceUsePermission();
- // TODO(b/201696614): Implement
- resultCallback.complete(RESULT_UNKNOWN);
+
+ throw new UnsupportedOperationException();
}
private void enforceUsePermission() {
@@ -123,9 +123,9 @@ public class AttestationVerificationManagerService extends SystemService {
AttestationProfile profile, int localBindingType, Bundle requirements,
byte[] attestation, AndroidFuture<IVerificationResult> resultCallback) {
IVerificationResult result = new IVerificationResult();
- // TODO(b/201696614): Implement
result.token = null;
- switch (profile.getAttestationProfileId()) {
+ int profileId = profile.getAttestationProfileId();
+ switch (profileId) {
case PROFILE_SELF_TRUSTED:
Slog.d(TAG, "Verifying Self Trusted profile.");
try {
@@ -133,7 +133,7 @@ public class AttestationVerificationManagerService extends SystemService {
AttestationVerificationSelfTrustedVerifierForTesting.getInstance()
.verifyAttestation(localBindingType, requirements, attestation);
} catch (Throwable t) {
- result.resultCode = RESULT_FAILURE;
+ result.resultCode = FLAG_FAILURE_CERTS;
}
break;
case PROFILE_PEER_DEVICE:
@@ -142,8 +142,8 @@ public class AttestationVerificationManagerService extends SystemService {
localBindingType, requirements, attestation);
break;
default:
- Slog.d(TAG, "No profile found, defaulting.");
- result.resultCode = RESULT_UNKNOWN;
+ Slog.e(TAG, "Profile [" + profileId + "] is not supported.");
+ result.resultCode = FLAG_FAILURE_UNSUPPORTED_PROFILE;
}
resultCallback.complete(result);
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index 41e3d00f3924..dc1f93664f79 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -16,11 +16,15 @@
package com.android.server.security;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_BOOT_STATE;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN;
import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS;
import static android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
import static android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY;
import static android.security.attestationverification.AttestationVerificationManager.localBindingTypeToString;
@@ -35,10 +39,8 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
-import android.security.attestationverification.AttestationVerificationManager;
import android.security.attestationverification.AttestationVerificationManager.LocalBindingType;
import android.util.IndentingPrintWriter;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
@@ -100,7 +102,6 @@ import java.util.Set;
*/
class AttestationVerificationPeerDeviceVerifier {
private static final String TAG = "AVF";
- private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
private static final int MAX_PATCH_AGE_MONTHS = 12;
/**
@@ -188,32 +189,24 @@ class AttestationVerificationPeerDeviceVerifier {
@NonNull byte[] attestation,
@NonNull MyDumpData dumpData) {
if (mCertificateFactory == null) {
- debugVerboseLog("Unable to access CertificateFactory");
- return RESULT_FAILURE;
+ Slog.e(TAG, "Unable to access CertificateFactory");
+ return FLAG_FAILURE_CERTS;
}
dumpData.mCertificationFactoryAvailable = true;
if (mCertPathValidator == null) {
- debugVerboseLog("Unable to access CertPathValidator");
- return RESULT_FAILURE;
+ Slog.e(TAG, "Unable to access CertPathValidator");
+ return FLAG_FAILURE_CERTS;
}
dumpData.mCertPathValidatorAvailable = true;
-
- // Check if the provided local binding type is supported and if the provided requirements
- // "match" the binding type.
- if (!validateAttestationParameters(localBindingType, requirements)) {
- return RESULT_FAILURE;
- }
- dumpData.mAttestationParametersOk = true;
-
// To provide the most information in the dump logs, we track the failure state but keep
// verifying the rest of the attestation. For code safety, there are no transitions past
- // here to set failed = false
- boolean failed = false;
+ // here to set result = 0.
+ int result = 0;
try {
- // First: parse and validate the certificate chain.
+ // 1. parse and validate the certificate chain.
final List<X509Certificate> certificateChain = getCertificates(attestation);
// (returns void, but throws CertificateException and other similar Exceptions)
validateCertificateChain(certificateChain);
@@ -222,33 +215,38 @@ class AttestationVerificationPeerDeviceVerifier {
final var leafCertificate = certificateChain.get(0);
final var attestationExtension = fromCertificate(leafCertificate);
- // Second: verify if the attestation satisfies the "peer device" profile.
- if (!checkAttestationForPeerDeviceProfile(requirements, attestationExtension,
- dumpData)) {
- failed = true;
+ // 2. Check if the provided local binding type is supported and if the provided
+ // requirements "match" the binding type.
+ if (!validateAttestationParameters(localBindingType, requirements)) {
+ return FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
}
+ dumpData.mAttestationParametersOk = true;
- // Third: check if the attestation satisfies local binding requirements.
+ // 3. check if the attestation satisfies local binding requirements.
if (!checkLocalBindingRequirements(
leafCertificate, attestationExtension, localBindingType, requirements,
dumpData)) {
- failed = true;
+ result |= FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
}
+
+ // 4. verify if the attestation satisfies the "peer device" profile.
+ result |= checkAttestationForPeerDeviceProfile(requirements, attestationExtension,
+ dumpData);
} catch (CertificateException | CertPathValidatorException
| InvalidAlgorithmParameterException | IOException e) {
- // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates()
+ // Catch all non-RuntimeExceptions (all of these are thrown by either getCertificates()
// or validateCertificateChain() or
// AndroidKeystoreAttestationVerificationAttributes.fromCertificate())
- debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e);
- failed = true;
+ Slog.e(TAG, "Unable to parse/validate Android Attestation certificate(s)", e);
+ result = FLAG_FAILURE_CERTS;
} catch (RuntimeException e) {
- // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions
- // out of this class/method.
- debugVerboseLog("Unexpected error", e);
- failed = true;
+ // Catch everything else (RuntimeExceptions), since we don't want to throw any
+ // exceptions out of this class/method.
+ Slog.e(TAG, "Unexpected error", e);
+ result = FLAG_FAILURE_UNKNOWN;
}
- return failed ? RESULT_FAILURE : RESULT_SUCCESS;
+ return result;
}
@NonNull
@@ -270,22 +268,22 @@ class AttestationVerificationPeerDeviceVerifier {
private boolean validateAttestationParameters(
@LocalBindingType int localBindingType, @NonNull Bundle requirements) {
if (localBindingType != TYPE_PUBLIC_KEY && localBindingType != TYPE_CHALLENGE) {
- debugVerboseLog("Binding type is not supported: " + localBindingType);
+ Slog.e(TAG, "Binding type is not supported: " + localBindingType);
return false;
}
if (requirements.size() < 1) {
- debugVerboseLog("At least 1 requirement is required.");
+ Slog.e(TAG, "At least 1 requirement is required.");
return false;
}
if (localBindingType == TYPE_PUBLIC_KEY && !requirements.containsKey(PARAM_PUBLIC_KEY)) {
- debugVerboseLog("Requirements does not contain key: " + PARAM_PUBLIC_KEY);
+ Slog.e(TAG, "Requirements does not contain key: " + PARAM_PUBLIC_KEY);
return false;
}
if (localBindingType == TYPE_CHALLENGE && !requirements.containsKey(PARAM_CHALLENGE)) {
- debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE);
+ Slog.e(TAG, "Requirements does not contain key: " + PARAM_CHALLENGE);
return false;
}
@@ -296,7 +294,7 @@ class AttestationVerificationPeerDeviceVerifier {
throws CertificateException, CertPathValidatorException,
InvalidAlgorithmParameterException {
if (certificates.size() < 2) {
- debugVerboseLog("Certificate chain less than 2 in size.");
+ Slog.e(TAG, "Certificate chain less than 2 in size.");
throw new CertificateException("Certificate chain less than 2 in size.");
}
@@ -355,7 +353,7 @@ class AttestationVerificationPeerDeviceVerifier {
final boolean publicKeyMatches = checkPublicKey(
leafCertificate, requirements.getByteArray(PARAM_PUBLIC_KEY));
if (!publicKeyMatches) {
- debugVerboseLog(
+ Slog.e(TAG,
"Provided public key does not match leaf certificate public key.");
return false;
}
@@ -366,7 +364,7 @@ class AttestationVerificationPeerDeviceVerifier {
final boolean attestationChallengeMatches = checkAttestationChallenge(
attestationAttributes, requirements.getByteArray(PARAM_CHALLENGE));
if (!attestationChallengeMatches) {
- debugVerboseLog(
+ Slog.e(TAG,
"Provided challenge does not match leaf certificate challenge.");
return false;
}
@@ -386,7 +384,7 @@ class AttestationVerificationPeerDeviceVerifier {
final boolean ownedBySystem = checkOwnedBySystem(
leafCertificate, attestationAttributes);
if (!ownedBySystem) {
- debugVerboseLog("Certificate public key is not owned by the AndroidSystem.");
+ Slog.e(TAG, "Certificate public key is not owned by the AndroidSystem.");
return false;
}
dumpData.mSystemOwned = true;
@@ -401,67 +399,67 @@ class AttestationVerificationPeerDeviceVerifier {
return true;
}
- private boolean checkAttestationForPeerDeviceProfile(
+ private int checkAttestationForPeerDeviceProfile(
@NonNull Bundle requirements,
@NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
MyDumpData dumpData) {
- boolean result = true;
+ int result = 0;
// Checks for support of Keymaster 4.
if (attestationAttributes.getAttestationVersion() < 3) {
- debugVerboseLog("Attestation version is not at least 3 (Keymaster 4).");
- result = false;
+ Slog.e(TAG, "Attestation version is not at least 3 (Keymaster 4).");
+ result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
} else {
dumpData.mAttestationVersionAtLeast3 = true;
}
// Checks for support of Keymaster 4.
if (attestationAttributes.getKeymasterVersion() < 4) {
- debugVerboseLog("Keymaster version is not at least 4.");
- result = false;
+ Slog.e(TAG, "Keymaster version is not at least 4.");
+ result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
} else {
dumpData.mKeymasterVersionAtLeast4 = true;
}
// First two characters are Android OS version.
if (attestationAttributes.getKeyOsVersion() < 100000) {
- debugVerboseLog("Android OS version is not 10+.");
- result = false;
+ Slog.e(TAG, "Android OS version is not 10+.");
+ result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
} else {
dumpData.mOsVersionAtLeast10 = true;
}
if (!attestationAttributes.isAttestationHardwareBacked()) {
- debugVerboseLog("Key is not HW backed.");
- result = false;
+ Slog.e(TAG, "Key is not HW backed.");
+ result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
} else {
dumpData.mKeyHwBacked = true;
}
if (!attestationAttributes.isKeymasterHardwareBacked()) {
- debugVerboseLog("Keymaster is not HW backed.");
- result = false;
+ Slog.e(TAG, "Keymaster is not HW backed.");
+ result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
} else {
dumpData.mKeymasterHwBacked = true;
}
if (attestationAttributes.getVerifiedBootState() != VERIFIED) {
- debugVerboseLog("Boot state not Verified.");
- result = false;
+ Slog.e(TAG, "Boot state not Verified.");
+ result |= FLAG_FAILURE_BOOT_STATE;
} else {
dumpData.mBootStateIsVerified = true;
}
try {
if (!attestationAttributes.isVerifiedBootLocked()) {
- debugVerboseLog("Verified boot state is not locked.");
- result = false;
+ Slog.e(TAG, "Verified boot state is not locked.");
+ result |= FLAG_FAILURE_BOOT_STATE;
} else {
dumpData.mVerifiedBootStateLocked = true;
}
} catch (IllegalStateException e) {
- debugVerboseLog("VerifiedBootLocked is not set.", e);
- result = false;
+ Slog.e(TAG, "VerifiedBootLocked is not set.", e);
+ result = FLAG_FAILURE_BOOT_STATE;
}
int maxPatchLevelDiffMonths = requirements.getInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS,
@@ -470,8 +468,8 @@ class AttestationVerificationPeerDeviceVerifier {
// Patch level integer YYYYMM is expected to be within maxPatchLevelDiffMonths of today.
if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel(),
maxPatchLevelDiffMonths)) {
- debugVerboseLog("OS patch level is not within valid range.");
- result = false;
+ Slog.e(TAG, "OS patch level is not within valid range.");
+ result |= FLAG_FAILURE_PATCH_LEVEL_DIFF;
} else {
dumpData.mOsPatchLevelInRange = true;
}
@@ -479,24 +477,24 @@ class AttestationVerificationPeerDeviceVerifier {
// Patch level integer YYYYMMDD is expected to be within maxPatchLevelDiffMonths of today.
if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(),
maxPatchLevelDiffMonths)) {
- debugVerboseLog("Boot patch level is not within valid range.");
- result = false;
+ Slog.e(TAG, "Boot patch level is not within valid range.");
+ result |= FLAG_FAILURE_PATCH_LEVEL_DIFF;
} else {
dumpData.mKeyBootPatchLevelInRange = true;
}
if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel(),
maxPatchLevelDiffMonths)) {
- debugVerboseLog("Vendor patch level is not within valid range.");
- result = false;
+ Slog.e(TAG, "Vendor patch level is not within valid range.");
+ result |= FLAG_FAILURE_PATCH_LEVEL_DIFF;
} else {
dumpData.mKeyVendorPatchLevelInRange = true;
}
if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(),
maxPatchLevelDiffMonths)) {
- debugVerboseLog("Boot patch level is not within valid range.");
- result = false;
+ Slog.e(TAG, "Boot patch level is not within valid range.");
+ result |= FLAG_FAILURE_PATCH_LEVEL_DIFF;
} else {
dumpData.mKeyBootPatchLevelInRange = true;
}
@@ -522,7 +520,7 @@ class AttestationVerificationPeerDeviceVerifier {
final Set<String> ownerPackages =
attestationAttributes.getApplicationPackageNameVersion().keySet();
if (!ANDROID_SYSTEM_PACKAGE_NAME_SET.equals(ownerPackages)) {
- debugVerboseLog("Owner is not system, packages=" + ownerPackages);
+ Slog.e(TAG, "Owner is not system, packages=" + ownerPackages);
return false;
}
@@ -548,7 +546,7 @@ class AttestationVerificationPeerDeviceVerifier {
localPatchDate = LocalDate.parse(Build.VERSION.SECURITY_PATCH);
}
} catch (Throwable t) {
- debugVerboseLog("Build.VERSION.SECURITY_PATCH: "
+ Slog.e(TAG, "Build.VERSION.SECURITY_PATCH: "
+ Build.VERSION.SECURITY_PATCH + " is not in format YYYY-MM-DD");
return false;
}
@@ -563,7 +561,7 @@ class AttestationVerificationPeerDeviceVerifier {
// Convert remote patch dates to LocalDate.
String remoteDeviceDateStr = String.valueOf(patchLevel);
if (remoteDeviceDateStr.length() != 6 && remoteDeviceDateStr.length() != 8) {
- debugVerboseLog("Patch level is not in format YYYYMM or YYYYMMDD");
+ Slog.e(TAG, "Patch level is not in format YYYYMM or YYYYMMDD");
return false;
}
@@ -666,18 +664,6 @@ class AttestationVerificationPeerDeviceVerifier {
}
}
- private static void debugVerboseLog(String str, Throwable t) {
- if (DEBUG) {
- Slog.v(TAG, str, t);
- }
- }
-
- private static void debugVerboseLog(String str) {
- if (DEBUG) {
- Slog.v(TAG, str);
- }
- }
-
/* Mutable data class for tracking dump data from verifications. */
private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
@@ -717,9 +703,7 @@ class AttestationVerificationPeerDeviceVerifier {
@SuppressLint("WrongConstant")
@Override
public void dumpTo(IndentingPrintWriter writer) {
- writer.println(
- "Result: " + AttestationVerificationManager.verificationResultCodeToString(
- mResult));
+ writer.println("Result: " + mResult);
if (!mCertificationFactoryAvailable) {
writer.println("Certificate Factory Unavailable");
return;
diff --git a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
index 58df2bd982dc..5039f6f42ac3 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
@@ -16,17 +16,15 @@
package com.android.server.security;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
-import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
import android.annotation.NonNull;
-import android.os.Build;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
@@ -64,7 +62,6 @@ import java.util.Set;
*/
class AttestationVerificationSelfTrustedVerifierForTesting {
private static final String TAG = "AVF";
- private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
// The OID for the extension Android Keymint puts into device-generated certificates.
private static final String ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID =
@@ -99,18 +96,6 @@ class AttestationVerificationSelfTrustedVerifierForTesting {
return sAttestationVerificationSelfTrustedVerifier;
}
- private static void debugVerboseLog(String str, Throwable t) {
- if (DEBUG) {
- Slog.v(TAG, str, t);
- }
- }
-
- private static void debugVerboseLog(String str) {
- if (DEBUG) {
- Slog.v(TAG, str);
- }
- }
-
private AttestationVerificationSelfTrustedVerifierForTesting() throws Exception {
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
@@ -142,23 +127,42 @@ class AttestationVerificationSelfTrustedVerifierForTesting {
certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis));
}
} catch (CertificateException e) {
- debugVerboseLog("Unable to parse certificates from attestation", e);
- return RESULT_FAILURE;
+ Slog.e("Unable to parse certificates from attestation", e.getLocalizedMessage());
+ return FLAG_FAILURE_CERTS;
}
- if (localBindingType == TYPE_CHALLENGE
- && validateRequirements(requirements)
- && checkLeafChallenge(requirements, certificates)
- && verifyCertificateChain(certificates)) {
- return RESULT_SUCCESS;
+ int result = 0;
+
+ if (localBindingType != TYPE_CHALLENGE
+ || !validateRequirements(requirements)) {
+ Slog.e(TAG, "Local binding requirements verification failure." + localBindingType);
+ return FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
+ }
+
+ // Verify challenge
+ byte[] challenge;
+ try {
+ challenge = getChallengeFromCert(certificates.get(0));
+ } catch (Throwable t) {
+ Slog.e("Unable to parse challenge from certificate.", t.getLocalizedMessage());
+ result |= FLAG_FAILURE_CERTS;
+ return result;
+ }
+ if (!Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) {
+ Slog.e(TAG, "Self-Trusted validation failed; challenge mismatch.");
+ result |= FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
}
- return RESULT_FAILURE;
+ if (!verifyCertificateChain(certificates)) {
+ result |= FLAG_FAILURE_CERTS;
+ }
+
+ return result;
}
private boolean verifyCertificateChain(List<X509Certificate> certificates) {
if (certificates.size() < 2) {
- debugVerboseLog("Certificate chain less than 2 in size.");
+ Slog.e(TAG, "Certificate chain less than 2 in size.");
return false;
}
@@ -170,7 +174,7 @@ class AttestationVerificationSelfTrustedVerifierForTesting {
validationParams.setRevocationEnabled(false);
mCertPathValidator.validate(certificatePath, validationParams);
} catch (Throwable t) {
- debugVerboseLog("Invalid certificate chain", t);
+ Slog.e(TAG, "Invalid certificate chain", t);
return false;
}
@@ -183,34 +187,16 @@ class AttestationVerificationSelfTrustedVerifierForTesting {
private boolean validateRequirements(Bundle requirements) {
if (requirements.size() != 1) {
- debugVerboseLog("Requirements does not contain exactly 1 key.");
+ Slog.e(TAG, "Requirements does not contain exactly 1 key.");
return false;
}
if (!requirements.containsKey(PARAM_CHALLENGE)) {
- debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE);
+ Slog.e(TAG, "Requirements does not contain key: " + PARAM_CHALLENGE);
return false;
}
return true;
}
- private boolean checkLeafChallenge(Bundle requirements, List<X509Certificate> certificates) {
- // Verify challenge
- byte[] challenge;
- try {
- challenge = getChallengeFromCert(certificates.get(0));
- } catch (Throwable t) {
- debugVerboseLog("Unable to parse challenge from certificate.", t);
- return false;
- }
-
- if (Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) {
- return true;
- } else {
- debugVerboseLog("Self-Trusted validation failed; challenge mismatch.");
- return false;
- }
- }
-
private byte[] getChallengeFromCert(@NonNull X509Certificate x509Certificate)
throws CertificateEncodingException, IOException {
Certificate certificate = Certificate.getInstance(
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
new file mode 100644
index 000000000000..7fb57085fb35
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection;
+
+import static android.provider.Settings.Secure.ADVANCED_PROTECTION_MODE;
+
+import android.Manifest;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PermissionEnforcer;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.provider.Settings;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.security.advancedprotection.IAdvancedProtectionCallback;
+import android.security.advancedprotection.IAdvancedProtectionService;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
+import com.android.server.security.advancedprotection.features.DisallowCellular2GAdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+
+/** @hide */
+public class AdvancedProtectionService extends IAdvancedProtectionService.Stub {
+ private static final String TAG = "AdvancedProtectionService";
+ private static final int MODE_CHANGED = 0;
+ private static final int CALLBACK_ADDED = 1;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final AdvancedProtectionStore mStore;
+
+ // Features living with the service - their code will be executed when state changes
+ private final ArrayList<AdvancedProtectionHook> mHooks = new ArrayList<>();
+ // External features - they will be called on state change
+ private final ArrayMap<IBinder, IAdvancedProtectionCallback> mCallbacks = new ArrayMap<>();
+ // For tracking only - not called on state change
+ private final ArrayList<AdvancedProtectionProvider> mProviders = new ArrayList<>();
+
+ private AdvancedProtectionService(@NonNull Context context) {
+ super(PermissionEnforcer.fromContext(context));
+ mContext = context;
+ mHandler = new AdvancedProtectionHandler(FgThread.get().getLooper());
+ mStore = new AdvancedProtectionStore(context);
+ }
+
+ private void initFeatures(boolean enabled) {
+ if (android.security.Flags.aapmFeatureDisableInstallUnknownSources()) {
+ try {
+ mHooks.add(new DisallowInstallUnknownSourcesAdvancedProtectionHook(mContext, enabled));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initialize DisallowInstallUnknownSources", e);
+ }
+ }
+ if (android.security.Flags.aapmFeatureMemoryTaggingExtension()) {
+ try {
+ mHooks.add(new MemoryTaggingExtensionHook(mContext, enabled));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initialize MemoryTaggingExtension", e);
+ }
+ }
+ if (android.security.Flags.aapmFeatureDisableCellular2g()) {
+ try {
+ mHooks.add(new DisallowCellular2GAdvancedProtectionHook(mContext, enabled));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initialize DisallowCellular2g", e);
+ }
+ }
+ }
+
+ // Only for tests
+ @VisibleForTesting
+ AdvancedProtectionService(@NonNull Context context, @NonNull AdvancedProtectionStore store,
+ @NonNull Looper looper, @NonNull PermissionEnforcer permissionEnforcer,
+ @Nullable AdvancedProtectionHook hook, @Nullable AdvancedProtectionProvider provider) {
+ super(permissionEnforcer);
+ mContext = context;
+ mStore = store;
+ mHandler = new AdvancedProtectionHandler(looper);
+ if (hook != null) {
+ mHooks.add(hook);
+ }
+
+ if (provider != null) {
+ mProviders.add(provider);
+ }
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE)
+ public boolean isAdvancedProtectionEnabled() {
+ isAdvancedProtectionEnabled_enforcePermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return isAdvancedProtectionEnabledInternal();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ // Without permission check
+ private boolean isAdvancedProtectionEnabledInternal() {
+ return mStore.retrieve();
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE)
+ public void registerAdvancedProtectionCallback(@NonNull IAdvancedProtectionCallback callback)
+ throws RemoteException {
+ registerAdvancedProtectionCallback_enforcePermission();
+ IBinder b = callback.asBinder();
+ b.linkToDeath(new DeathRecipient(b), 0);
+ synchronized (mCallbacks) {
+ mCallbacks.put(b, callback);
+ sendCallbackAdded(isAdvancedProtectionEnabledInternal(), callback);
+ }
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE)
+ public void unregisterAdvancedProtectionCallback(
+ @NonNull IAdvancedProtectionCallback callback) {
+ unregisterAdvancedProtectionCallback_enforcePermission();
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback.asBinder());
+ }
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
+ public void setAdvancedProtectionEnabled(boolean enabled) {
+ setAdvancedProtectionEnabled_enforcePermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mCallbacks) {
+ if (enabled != isAdvancedProtectionEnabledInternal()) {
+ mStore.store(enabled);
+ sendModeChanged(enabled);
+ Slog.i(TAG, "Advanced protection is " + (enabled ? "enabled" : "disabled"));
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
+ public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() {
+ getAdvancedProtectionFeatures_enforcePermission();
+ List<AdvancedProtectionFeature> features = new ArrayList<>();
+ for (int i = 0; i < mProviders.size(); i++) {
+ features.addAll(mProviders.get(i).getFeatures());
+ }
+
+ for (int i = 0; i < mHooks.size(); i++) {
+ AdvancedProtectionHook hook = mHooks.get(i);
+ if (hook.isAvailable()) {
+ features.add(hook.getFeature());
+ }
+ }
+
+ return features;
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, @NonNull String[] args, ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ (new AdvancedProtectionShellCommand(this))
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ void sendModeChanged(boolean enabled) {
+ Message.obtain(mHandler, MODE_CHANGED, /*enabled*/ enabled ? 1 : 0, /*unused */ -1)
+ .sendToTarget();
+ }
+
+ void sendCallbackAdded(boolean enabled, IAdvancedProtectionCallback callback) {
+ Message.obtain(mHandler, CALLBACK_ADDED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1,
+ /*callback*/ callback)
+ .sendToTarget();
+ }
+
+ public static final class Lifecycle extends SystemService {
+ private final AdvancedProtectionService mService;
+
+ public Lifecycle(@NonNull Context context) {
+ super(context);
+ mService = new AdvancedProtectionService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.ADVANCED_PROTECTION_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(@BootPhase int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ boolean enabled = mService.isAdvancedProtectionEnabledInternal();
+ if (enabled) {
+ Slog.i(TAG, "Advanced protection is enabled");
+ }
+ mService.initFeatures(enabled);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static class AdvancedProtectionStore {
+ private final Context mContext;
+ private static final int APM_ON = 1;
+ private static final int APM_OFF = 0;
+ private final UserManagerInternal mUserManager;
+
+ AdvancedProtectionStore(@NonNull Context context) {
+ mContext = context;
+ mUserManager = LocalServices.getService(UserManagerInternal.class);
+ }
+
+ void store(boolean enabled) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ ADVANCED_PROTECTION_MODE, enabled ? APM_ON : APM_OFF,
+ mUserManager.getMainUserId());
+ }
+
+ boolean retrieve() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ ADVANCED_PROTECTION_MODE, APM_OFF, mUserManager.getMainUserId()) == APM_ON;
+ }
+ }
+
+ private class AdvancedProtectionHandler extends Handler {
+ private AdvancedProtectionHandler(@NonNull Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ //arg1 == enabled
+ case MODE_CHANGED:
+ handleAllCallbacks(msg.arg1 == 1);
+ break;
+ //arg1 == enabled
+ //obj == callback
+ case CALLBACK_ADDED:
+ handleSingleCallback(msg.arg1 == 1, (IAdvancedProtectionCallback) msg.obj);
+ break;
+ }
+ }
+
+ private void handleAllCallbacks(boolean enabled) {
+ ArrayList<IAdvancedProtectionCallback> deadObjects = new ArrayList<>();
+
+ for (int i = 0; i < mHooks.size(); i++) {
+ AdvancedProtectionHook feature = mHooks.get(i);
+ try {
+ if (feature.isAvailable()) {
+ feature.onAdvancedProtectionChanged(enabled);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to call hook for feature "
+ + feature.getFeature().getId(), e);
+ }
+ }
+ synchronized (mCallbacks) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ IAdvancedProtectionCallback callback = mCallbacks.valueAt(i);
+ try {
+ callback.onAdvancedProtectionChanged(enabled);
+ } catch (RemoteException e) {
+ deadObjects.add(callback);
+ }
+ }
+
+ for (int i = 0; i < deadObjects.size(); i++) {
+ mCallbacks.remove(deadObjects.get(i).asBinder());
+ }
+ }
+ }
+
+ private void handleSingleCallback(boolean enabled, IAdvancedProtectionCallback callback) {
+ try {
+ callback.onAdvancedProtectionChanged(enabled);
+ } catch (RemoteException e) {
+ mCallbacks.remove(callback.asBinder());
+ }
+ }
+ }
+
+ private final class DeathRecipient implements IBinder.DeathRecipient {
+ private final IBinder mBinder;
+
+ DeathRecipient(IBinder binder) {
+ mBinder = binder;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(mBinder);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java
new file mode 100644
index 000000000000..42505ad2de3f
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+class AdvancedProtectionShellCommand extends ShellCommand {
+ private AdvancedProtectionService mService;
+
+ AdvancedProtectionShellCommand(@NonNull AdvancedProtectionService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "help":
+ onHelp();
+ return 0;
+ case "set-protection-enabled":
+ return setProtectionEnabled();
+ case "is-protection-enabled":
+ return isProtectionEnabled(pw);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ dumpHelp(pw);
+ }
+
+ private void dumpHelp(@NonNull PrintWriter pw) {
+ pw.println("Advanced Protection Mode commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" set-protection-enabled [true|false]");
+ pw.println(" is-protection-enabled");
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private int setProtectionEnabled() throws RemoteException {
+ String protectionMode = getNextArgRequired();
+ mService.setAdvancedProtectionEnabled(Boolean.parseBoolean(protectionMode));
+ return 0;
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private int isProtectionEnabled(@NonNull PrintWriter pw) throws RemoteException {
+ boolean protectionMode = mService.isAdvancedProtectionEnabled();
+ pw.println(protectionMode);
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
new file mode 100644
index 000000000000..f82db9676c2c
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+
+/** @hide */
+public abstract class AdvancedProtectionHook {
+ /** Called on boot phase PHASE_SYSTEM_SERVICES_READY */
+ public AdvancedProtectionHook(@NonNull Context context, boolean enabled) {}
+ /** The feature this hook provides */
+ @NonNull
+ public abstract AdvancedProtectionFeature getFeature();
+ /** Whether this feature is relevant on this device. If false, onAdvancedProtectionChanged will
+ * not be called, and the feature will not be displayed in the onboarding UX. */
+ public abstract boolean isAvailable();
+ /** Called whenever advanced protection state changes */
+ public void onAdvancedProtectionChanged(boolean enabled) {}
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
new file mode 100644
index 000000000000..ed451f1e2257
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import android.security.advancedprotection.AdvancedProtectionFeature;
+
+import java.util.List;
+
+/** @hide */
+public abstract class AdvancedProtectionProvider {
+ /** The list of features provided */
+ public abstract List<AdvancedProtectionFeature> getFeatures();
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
new file mode 100644
index 000000000000..f51c25d6761c
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** @hide */
+public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
+ private static final String TAG = "AdvancedProtectionDisallowCellular2G";
+
+ private final AdvancedProtectionFeature mFeature =
+ new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final TelephonyManager mTelephonyManager;
+ private final SubscriptionManager mSubscriptionManager;
+
+ public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
+ super(context, enabled);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+
+ setPolicy(enabled);
+ }
+
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ private static boolean isEmbeddedSubscriptionVisible(SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded()
+ && (subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING
+ || (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()
+ && subInfo.isOnlyNonTerrestrialNetwork()))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private List<TelephonyManager> getActiveTelephonyManagers() {
+ List<TelephonyManager> telephonyManagers = new ArrayList<>();
+
+ for (SubscriptionInfo subInfo : mSubscriptionManager.getActiveSubscriptionInfoList()) {
+ if (isEmbeddedSubscriptionVisible(subInfo)) {
+ telephonyManagers.add(
+ mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()));
+ }
+ }
+
+ return telephonyManagers;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ if (telephonyManager.isDataCapable()
+ && telephonyManager.isRadioInterfaceCapabilitySupported(
+ mTelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void setPolicy(boolean enabled) {
+ if (enabled) {
+ Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+ mDevicePolicyManager.addUserRestrictionGlobally(
+ ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+ } else {
+ Slog.d(TAG, "Clearing DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+ mDevicePolicyManager.clearUserRestrictionGlobally(
+ ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+ }
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ setPolicy(enabled);
+
+ // Leave 2G disabled even if APM is disabled.
+ if (!enabled) {
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ long oldAllowedTypes =
+ telephonyManager.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ telephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
new file mode 100644
index 000000000000..bb523d63c43a
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+/** @hide */
+public final class DisallowInstallUnknownSourcesAdvancedProtectionHook
+ extends AdvancedProtectionHook {
+ private static final String TAG = "AdvancedProtectionDisallowInstallUnknown";
+
+ private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
+ FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES);
+
+ private final ActivityManagerInternal mActivityManagerInternal;
+ private final AppOpsManager mAppOpsManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final IPackageManager mIPackageManager;
+ private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
+
+ public DisallowInstallUnknownSourcesAdvancedProtectionHook(@NonNull Context context,
+ boolean enabled) {
+ super(context, enabled);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mIPackageManager = AppGlobals.getPackageManager();
+ mUserManager = context.getSystemService(UserManager.class);
+ mPackageManager = context.getPackageManager();
+
+ setRestriction(enabled);
+ }
+
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ private void setRestriction(boolean enabled) {
+ if (enabled) {
+ Slog.d(TAG, "Setting DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
+ mDevicePolicyManager.addUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+ } else {
+ Slog.d(TAG, "Clearing DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
+ mDevicePolicyManager.clearUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+ }
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ setRestriction(enabled);
+ if (enabled) return;
+
+ // Leave OP_REQUEST_INSTALL_PACKAGES disabled when APM is disabled.
+ Slog.d(TAG, "Setting all OP_REQUEST_INSTALL_PACKAGES to MODE_ERRORED");
+ for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+ try {
+ final String[] packagesWithRequestInstallPermission = mIPackageManager
+ .getAppOpPermissionPackages(
+ Manifest.permission.REQUEST_INSTALL_PACKAGES, userInfo.id);
+ for (String packageName : packagesWithRequestInstallPermission) {
+ try {
+ int uid = mPackageManager.getPackageUidAsUser(packageName, userInfo.id);
+ boolean isCallerInstrumented = mActivityManagerInternal
+ .getInstrumentationSourceUid(uid) != Process.INVALID_UID;
+ if (!isCallerInstrumented) {
+ mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid,
+ packageName, AppOpsManager.MODE_ERRORED);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Couldn't retrieve uid for a package: " + e);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't retrieve packages with REQUEST_INSTALL_PACKAGES."
+ + " getAppOpPermissionPackages() threw the following exception: " + e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java
new file mode 100644
index 000000000000..d3d39371e883
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.util.Slog;
+
+/** @hide */
+public final class MemoryTaggingExtensionHook
+ extends AdvancedProtectionHook {
+ private static final String TAG = "AdvancedProtectionMTE";
+ private static final String MTE_DPM_SYSTEM_PROPERTY =
+ "ro.arm64.memtag.bootctl_device_policy_manager";
+ private static final String MTE_SETTINGS_SYSTEM_PROPERTY =
+ "ro.arm64.memtag.bootctl_settings_toggle";
+
+ private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
+ FEATURE_ID_ENABLE_MTE);
+ private final DevicePolicyManager mDevicePolicyManager;
+
+ public MemoryTaggingExtensionHook(@NonNull Context context,
+ boolean enabled) {
+ super(context, enabled);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ onAdvancedProtectionChanged(enabled);
+ }
+
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return SystemProperties.getBoolean(MTE_DPM_SYSTEM_PROPERTY,
+ SystemProperties.getBoolean(MTE_SETTINGS_SYSTEM_PROPERTY, false));
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ if (!isAvailable()) {
+ Slog.i(TAG, "MTE unavailable on device, skipping.");
+ return;
+ }
+ final int mtePolicy;
+ if (enabled) {
+ mtePolicy = DevicePolicyManager.MTE_ENABLED;
+ } else {
+ mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ Slog.d(TAG, "Setting MTE state to " + mtePolicy);
+ try {
+ mDevicePolicyManager.setMtePolicy(ADVANCED_PROTECTION_SYSTEM_ENTITY, mtePolicy);
+ } catch (UnsupportedOperationException e) {
+ Slog.i(TAG, "Setting MTE policy unsupported", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
index 9398c7a854d4..6798a6146ae0 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package com.android.server.adaptiveauth;
+package com.android.server.security.authenticationpolicy;
+
+import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
+import android.annotation.EnforcePermission;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -32,9 +35,14 @@ import android.hardware.biometrics.events.AuthenticationStoppedInfo;
import android.hardware.biometrics.events.AuthenticationSucceededInfo;
import android.os.Build;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+import android.security.authenticationpolicy.IAuthenticationPolicyService;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -55,8 +63,8 @@ import java.util.Objects;
/**
* @hide
*/
-public class AdaptiveAuthService extends SystemService {
- private static final String TAG = "AdaptiveAuthService";
+public class AuthenticationPolicyService extends SystemService {
+ private static final String TAG = "AuthenticationPolicyService";
private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
@VisibleForTesting
@@ -74,16 +82,17 @@ public class AdaptiveAuthService extends SystemService {
private final KeyguardManager mKeyguardManager;
private final WindowManagerInternal mWindowManager;
private final UserManagerInternal mUserManager;
+ private SecureLockDeviceServiceInternal mSecureLockDeviceService;
@VisibleForTesting
final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
- public AdaptiveAuthService(Context context) {
+ public AuthenticationPolicyService(Context context) {
this(context, new LockPatternUtils(context));
}
@VisibleForTesting
- public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) {
+ public AuthenticationPolicyService(Context context, LockPatternUtils lockPatternUtils) {
super(context);
mLockPatternUtils = lockPatternUtils;
mLockSettings = Objects.requireNonNull(
@@ -94,10 +103,16 @@ public class AdaptiveAuthService extends SystemService {
mWindowManager = Objects.requireNonNull(
LocalServices.getService(WindowManagerInternal.class));
mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
+ if (android.security.Flags.secureLockdown()) {
+ mSecureLockDeviceService = Objects.requireNonNull(
+ LocalServices.getService(SecureLockDeviceServiceInternal.class));
+ }
}
@Override
- public void onStart() {}
+ public void onStart() {
+ publishBinderService(Context.AUTHENTICATION_POLICY_SERVICE, mService);
+ }
@Override
public void onBootPhase(int phase) {
@@ -294,4 +309,36 @@ public class AdaptiveAuthService extends SystemService {
// next successful primary or biometric auth happens
mLastLockedTimestamp.put(userId, SystemClock.elapsedRealtime());
}
+
+ private final IBinder mService = new IAuthenticationPolicyService.Stub() {
+ /**
+ * @see AuthenticationPolicyManager#enableSecureLockDevice(EnableSecureLockDeviceParams)
+ * @param params EnableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the Secure
+ * Lock Device request
+ */
+ @Override
+ @EnforcePermission(MANAGE_SECURE_LOCK_DEVICE)
+ @AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus
+ public int enableSecureLockDevice(EnableSecureLockDeviceParams params) {
+ enableSecureLockDevice_enforcePermission();
+ return mSecureLockDeviceService.enableSecureLockDevice(params);
+ }
+
+ /**
+ * @see AuthenticationPolicyManager#disableSecureLockDevice(DisableSecureLockDeviceParams)
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the Secure
+ * Lock Device request
+ */
+ @Override
+ @EnforcePermission(MANAGE_SECURE_LOCK_DEVICE)
+ @AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus
+ public int disableSecureLockDevice(DisableSecureLockDeviceParams params) {
+ disableSecureLockDevice_enforcePermission();
+ return mSecureLockDeviceService.disableSecureLockDevice(params);
+ }
+ };
}
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/OWNERS b/services/core/java/com/android/server/security/authenticationpolicy/OWNERS
new file mode 100644
index 000000000000..29affcdb81aa
--- /dev/null
+++ b/services/core/java/com/android/server/security/authenticationpolicy/OWNERS
@@ -0,0 +1,3 @@
+hainingc@google.com
+jbolinger@google.com
+graciecheng@google.com
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java
new file mode 100644
index 000000000000..7b89723deb6c
--- /dev/null
+++ b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.authenticationpolicy;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+/**
+ * System service for remotely calling secure lock on the device.
+ *
+ * Callers will access this class via
+ * {@link com.android.server.security.authenticationpolicy.AuthenticationPolicyService}.
+ *
+ * @see AuthenticationPolicyService
+ * @see AuthenticationPolicyManager#enableSecureLockDevice
+ * @see AuthenticationPolicyManager#disableSecureLockDevice
+ * @hide
+ */
+public class SecureLockDeviceService extends SecureLockDeviceServiceInternal {
+ private static final String TAG = "SecureLockDeviceService";
+ private final Context mContext;
+
+ public SecureLockDeviceService(@NonNull Context context) {
+ mContext = context;
+ }
+
+ private void start() {
+ // Expose private service for system components to use.
+ LocalServices.addService(SecureLockDeviceServiceInternal.class, this);
+ }
+
+ /**
+ * @see AuthenticationPolicyManager#enableSecureLockDevice
+ * @param params EnableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock device request
+ *
+ * @hide
+ */
+ @Override
+ @EnableSecureLockDeviceRequestStatus
+ public int enableSecureLockDevice(EnableSecureLockDeviceParams params) {
+ // (1) Call into system_server to lock device, configure allowed auth types
+ // for secure lock
+ // TODO: lock device, configure allowed authentication types for device entry
+ // (2) Call into framework to configure secure lock 2FA lockscreen
+ // update, UI & string updates
+ // TODO: implement 2FA lockscreen when SceneContainerFlag.isEnabled()
+ // TODO: implement 2FA lockscreen when !SceneContainerFlag.isEnabled()
+ // (3) Call into framework to configure keyguard security updates
+ // TODO: implement security updates
+ return AuthenticationPolicyManager.ERROR_UNSUPPORTED;
+ }
+
+ /**
+ * @see AuthenticationPolicyManager#disableSecureLockDevice
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock device request
+ *
+ * @hide
+ */
+ @Override
+ @DisableSecureLockDeviceRequestStatus
+ public int disableSecureLockDevice(DisableSecureLockDeviceParams params) {
+ // (1) Call into system_server to reset allowed auth types
+ // TODO: reset allowed authentication types for device entry;
+ // (2) Call into framework to disable secure lock 2FA lockscreen, reset UI
+ // & string updates
+ // TODO: implement reverting to normal lockscreen when SceneContainerFlag.isEnabled()
+ // TODO: implement reverting to normal lockscreen when !SceneContainerFlag.isEnabled()
+ // (3) Call into framework to revert keyguard security updates
+ // TODO: implement reverting security updates
+ return AuthenticationPolicyManager.ERROR_UNSUPPORTED;
+ }
+
+ /**
+ * System service lifecycle.
+ */
+ public static final class Lifecycle extends SystemService {
+ private final SecureLockDeviceService mService;
+
+ public Lifecycle(@NonNull Context context) {
+ super(context);
+ mService = new SecureLockDeviceService(context);
+ }
+
+ @Override
+ public void onStart() {
+ Slog.i(TAG, "Starting SecureLockDeviceService");
+ mService.start();
+ Slog.i(TAG, "Started SecureLockDeviceService");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java
new file mode 100644
index 000000000000..b90370956d8b
--- /dev/null
+++ b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.authenticationpolicy;
+
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+
+/**
+ * Local system service interface for {@link SecureLockDeviceService}.
+ *
+ * <p>No permission / argument checks will be performed inside.
+ * Callers must check the calling app permission and the calling package name.
+ *
+ * @hide
+ */
+public abstract class SecureLockDeviceServiceInternal {
+ private static final String TAG = "SecureLockDeviceServiceInternal";
+
+ /**
+ * @see AuthenticationPolicyManager#enableSecureLockDevice(EnableSecureLockDeviceParams)
+ * @param params EnableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock request
+ */
+ @EnableSecureLockDeviceRequestStatus
+ public abstract int enableSecureLockDevice(EnableSecureLockDeviceParams params);
+
+ /**
+ * @see AuthenticationPolicyManager#disableSecureLockDevice(DisableSecureLockDeviceParams)
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock device request
+ */
+ @DisableSecureLockDeviceRequestStatus
+ public abstract int disableSecureLockDevice(DisableSecureLockDeviceParams params);
+}
diff --git a/services/core/java/com/android/server/security/forensic/OWNERS b/services/core/java/com/android/server/security/forensic/OWNERS
deleted file mode 100644
index 3bc3eb5d1412..000000000000
--- a/services/core/java/com/android/server/security/forensic/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:platform/frameworks/base:main:/core/java/android/security/forensic/OWNERS
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
new file mode 100644
index 000000000000..687442b47fb3
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class DataAggregator {
+ private static final String TAG = "IntrusionDetection DataAggregator";
+ private static final int MSG_SINGLE_DATA = 0;
+ private static final int MSG_BATCH_DATA = 1;
+ private static final int MSG_DISABLE = 2;
+
+ private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+
+ private final IntrusionDetectionService mIntrusionDetectionService;
+ private final ArrayList<DataSource> mDataSources;
+ private final AtomicBoolean mIsLoggingInitialized = new AtomicBoolean(false);
+
+ private Context mContext;
+ private List<IntrusionDetectionEvent> mStoredEvents = new ArrayList<>();
+ private ServiceThread mHandlerThread;
+ private Handler mHandler;
+
+ public DataAggregator(Context context, IntrusionDetectionService intrusionDetectionService) {
+ mIntrusionDetectionService = intrusionDetectionService;
+ mContext = context;
+ mDataSources = new ArrayList<DataSource>();
+ }
+
+ @VisibleForTesting
+ void setHandler(Looper looper, ServiceThread serviceThread) {
+ mHandlerThread = serviceThread;
+ mHandler = new EventHandler(looper, this);
+ }
+
+ /** Initialize DataSources */
+ private void initialize() {
+ mDataSources.add(new SecurityLogSource(mContext, this));
+ mDataSources.add(new NetworkLogSource(mContext, this));
+ }
+
+ /**
+ * Enable the data collection of all DataSources.
+ */
+ public void enable() {
+ if (!mIsLoggingInitialized.get()) {
+ initialize();
+ mIsLoggingInitialized.set(true);
+ }
+ mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
+ /* allowIo */ false);
+ mHandlerThread.start();
+ mHandler = new EventHandler(mHandlerThread.getLooper(), this);
+ for (DataSource ds : mDataSources) {
+ ds.enable();
+ }
+ }
+
+ /**
+ * DataSource calls it to transmit a single event.
+ */
+ public void addSingleData(IntrusionDetectionEvent event) {
+ mHandler.obtainMessage(MSG_SINGLE_DATA, event).sendToTarget();
+ }
+
+ /**
+ * DataSource calls it to transmit list of events.
+ */
+ public void addBatchData(List<IntrusionDetectionEvent> events) {
+ mHandler.obtainMessage(MSG_BATCH_DATA, events).sendToTarget();
+ }
+
+ /**
+ * Disable the data collection of all DataSources.
+ */
+ public void disable() {
+ mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
+ }
+
+ private void onNewSingleData(IntrusionDetectionEvent event) {
+ if (mStoredEvents.size() < STORED_EVENTS_SIZE_LIMIT) {
+ mStoredEvents.add(event);
+ } else {
+ mIntrusionDetectionService.addNewData(mStoredEvents);
+ mStoredEvents = new ArrayList<>();
+ }
+ }
+
+ private void onNewBatchData(List<IntrusionDetectionEvent> events) {
+ mIntrusionDetectionService.addNewData(events);
+ }
+
+ private void onDisable() {
+ for (DataSource ds : mDataSources) {
+ ds.disable();
+ }
+ mHandlerThread.quitSafely();
+ mHandlerThread = null;
+ }
+
+ private static class EventHandler extends Handler {
+ private final DataAggregator mDataAggregator;
+ EventHandler(Looper looper, DataAggregator dataAggregator) {
+ super(looper);
+ mDataAggregator = dataAggregator;
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SINGLE_DATA:
+ mDataAggregator.onNewSingleData((IntrusionDetectionEvent) msg.obj);
+ break;
+ case MSG_BATCH_DATA:
+ mDataAggregator.onNewBatchData((List<IntrusionDetectionEvent>) msg.obj);
+ break;
+ case MSG_DISABLE:
+ mDataAggregator.onDisable();
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
new file mode 100644
index 000000000000..0bc448245b76
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+public interface DataSource {
+ /**
+ * Enable the data collection.
+ */
+ void enable();
+
+ /**
+ * Disable the data collection.
+ */
+ void disable();
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
new file mode 100644
index 000000000000..a16e66de09a9
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Process;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+
+public class IntrusionDetectionEventTransportConnection implements ServiceConnection {
+ private static final String PRODUCTION_BUILD = "user";
+ private static final String PROPERTY_BUILD_TYPE = "ro.build.type";
+ private static final String PROPERTY_INTRUSION_DETECTION_SERVICE_NAME =
+ "debug.intrusiondetection_package_name";
+ private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 min
+ private static final String TAG = "IntrusionDetectionEventTransportConnection";
+ private final Context mContext;
+ private String mIntrusionDetectionEventTransportConfig;
+ volatile IIntrusionDetectionEventTransport mService;
+
+
+ public IntrusionDetectionEventTransportConnection(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Initialize the IntrusionDetectionEventTransport binder service.
+ *
+ * @return Whether the initialization succeeds.
+ */
+ public boolean initialize() {
+ Slog.d(TAG, "initialize");
+ if (!bindService()) {
+ return false;
+ }
+ // Wait for the service to be connected before calling initialize.
+ waitForConnection();
+ AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
+ try {
+ mService.initialize(resultFuture);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ unbindService();
+ return false;
+ }
+ Boolean result = getFutureResult(resultFuture);
+ if (result != null && result == true) {
+ return true;
+ } else {
+ unbindService();
+ return false;
+ }
+ }
+
+ private void waitForConnection() {
+ synchronized (this) {
+ while (mService == null) {
+ Slog.d(TAG, "waiting for connection to service...");
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ /* never interrupted */
+ }
+ }
+ Slog.d(TAG, "connected to service");
+ }
+ }
+
+ /**
+ * Add data to the IntrusionDetectionEventTransport binder service.
+ * @param data List of IntrusionDetectionEvent.
+ * @return Whether the data is added to the binder service.
+ */
+ public boolean addData(List<IntrusionDetectionEvent> data) {
+ AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
+ try {
+ mService.addData(data, resultFuture);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ return false;
+ }
+ Boolean result = getFutureResult(resultFuture);
+ return result != null && result == true;
+ }
+
+ /**
+ * Release the BackupTransport binder service.
+ */
+ public void release() {
+ AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
+ try {
+ mService.release(resultFuture);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ } finally {
+ unbindService();
+ }
+ }
+
+ private <T> T getFutureResult(AndroidFuture<T> future) {
+ try {
+ return future.get(FUTURE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException
+ | CancellationException e) {
+ Slog.e(TAG, "Failed to get result from transport:", e);
+ return null;
+ }
+ }
+
+ private String getSystemPropertyValue(String propertyName) {
+ String commandString = "getprop " + propertyName;
+ try {
+ Process process = Runtime.getRuntime().exec(commandString);
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String propertyValue = reader.readLine();
+ reader.close();
+ return propertyValue;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get system property value:", e);
+ return null;
+ }
+ }
+
+ private boolean bindService() {
+ String buildType = getSystemPropertyValue(PROPERTY_BUILD_TYPE);
+ mIntrusionDetectionEventTransportConfig =
+ mContext.getString(
+ com.android.internal.R.string.config_intrusionDetectionEventTransport);
+
+ // If the build type is not production, and a property value is set, use it instead.
+ // This allows us to test the service with a different config.
+ if (!buildType.equals(PRODUCTION_BUILD)
+ && !TextUtils.isEmpty(
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME))) {
+ mIntrusionDetectionEventTransportConfig =
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME);
+ }
+ Slog.d(
+ TAG,
+ "mIntrusionDetectionEventTransportConfig: "
+ + mIntrusionDetectionEventTransportConfig);
+
+ if (TextUtils.isEmpty(mIntrusionDetectionEventTransportConfig)) {
+ Slog.e(TAG, "Unable to find a valid config for the transport service");
+ return false;
+ }
+
+ ComponentName serviceComponent =
+ ComponentName.unflattenFromString(mIntrusionDetectionEventTransportConfig);
+ if (serviceComponent == null) {
+ Slog.e(TAG, "Can't get serviceComponent name");
+ return false;
+ }
+
+ try {
+ ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo(serviceComponent,
+ 0 /* flags */);
+ if (!BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE.equals(serviceInfo.permission)) {
+ Slog.e(TAG, serviceComponent.flattenToShortString()
+ + " is not declared with the permission "
+ + "\"" + BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE + "\"");
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Unable to find serviceComponent");
+ return false;
+ }
+
+ Intent intent = new Intent().setComponent(serviceComponent);
+ boolean result = mContext.bindServiceAsUser(
+ intent, this, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ if (!result) {
+ unbindService();
+ }
+ return result;
+ }
+
+ private void unbindService() {
+ mContext.unbindService(this);
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ Slog.d(TAG, "connected to service");
+ this.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ mService = null;
+ Slog.d(TAG, "disconnected from service");
+ this.notifyAll();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
new file mode 100644
index 000000000000..8ff1c7f22ffb
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
+import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
+
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PermissionEnforcer;
+import android.os.RemoteException;
+import android.security.intrusiondetection.IIntrusionDetectionService;
+import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback;
+import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class IntrusionDetectionService extends SystemService {
+ private static final String TAG = "IntrusionDetectionService";
+
+ private static final int MAX_STATE_CALLBACK_NUM = 16;
+ private static final int MSG_ADD_STATE_CALLBACK = 0;
+ private static final int MSG_REMOVE_STATE_CALLBACK = 1;
+ private static final int MSG_ENABLE = 2;
+ private static final int MSG_DISABLE = 3;
+ private static final int MSG_TRANSPORT = 4;
+
+ private static final int STATE_UNKNOWN =
+ IIntrusionDetectionServiceStateCallback.State.UNKNOWN;
+ private static final int STATE_DISABLED =
+ IIntrusionDetectionServiceStateCallback.State.DISABLED;
+ private static final int STATE_ENABLED =
+ IIntrusionDetectionServiceStateCallback.State.ENABLED;
+
+ private static final int ERROR_UNKNOWN =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.UNKNOWN;
+ private static final int ERROR_PERMISSION_DENIED =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
+ private static final int ERROR_INVALID_STATE_TRANSITION =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION;
+ private static final int ERROR_TRANSPORT_UNAVAILABLE =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE;
+ private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final IntrusionDetectionEventTransportConnection
+ mIntrusionDetectionEventTransportConnection;
+ private final DataAggregator mDataAggregator;
+ private final BinderService mBinderService;
+
+ private final ArrayList<IIntrusionDetectionServiceStateCallback> mStateCallbacks =
+ new ArrayList<>();
+ private volatile int mState = STATE_DISABLED;
+
+ public IntrusionDetectionService(@NonNull Context context) {
+ this(new InjectorImpl(context));
+ }
+
+ @VisibleForTesting
+ IntrusionDetectionService(@NonNull Injector injector) {
+ super(injector.getContext());
+ mContext = injector.getContext();
+ mHandler = new EventHandler(injector.getLooper(), this);
+ mIntrusionDetectionEventTransportConnection =
+ injector.getIntrusionDetectionEventransportConnection();
+ mDataAggregator = injector.getDataAggregator(this);
+ mBinderService = new BinderService(this, injector.getPermissionEnforcer());
+ }
+
+ @VisibleForTesting
+ protected void setState(int state) {
+ mState = state;
+ }
+
+ private static final class BinderService extends IIntrusionDetectionService.Stub {
+ final IntrusionDetectionService mService;
+
+ BinderService(IntrusionDetectionService service,
+ @NonNull PermissionEnforcer permissionEnforcer) {
+ super(permissionEnforcer);
+ mService = service;
+ }
+
+ @Override
+ @EnforcePermission(READ_INTRUSION_DETECTION_STATE)
+ public void addStateCallback(IIntrusionDetectionServiceStateCallback callback) {
+ addStateCallback_enforcePermission();
+ mService.mHandler.obtainMessage(MSG_ADD_STATE_CALLBACK, callback).sendToTarget();
+ }
+
+ @Override
+ @EnforcePermission(READ_INTRUSION_DETECTION_STATE)
+ public void removeStateCallback(IIntrusionDetectionServiceStateCallback callback) {
+ removeStateCallback_enforcePermission();
+ mService.mHandler.obtainMessage(MSG_REMOVE_STATE_CALLBACK, callback).sendToTarget();
+ }
+
+ @Override
+ @EnforcePermission(MANAGE_INTRUSION_DETECTION_STATE)
+ public void enable(IIntrusionDetectionServiceCommandCallback callback) {
+ enable_enforcePermission();
+ mService.mHandler.obtainMessage(MSG_ENABLE, callback).sendToTarget();
+ }
+
+ @Override
+ @EnforcePermission(MANAGE_INTRUSION_DETECTION_STATE)
+ public void disable(IIntrusionDetectionServiceCommandCallback callback) {
+ disable_enforcePermission();
+ mService.mHandler.obtainMessage(MSG_DISABLE, callback).sendToTarget();
+ }
+ }
+
+ private static class EventHandler extends Handler {
+ private final IntrusionDetectionService mService;
+
+ EventHandler(Looper looper, IntrusionDetectionService service) {
+ super(looper);
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ADD_STATE_CALLBACK:
+ try {
+ mService.addStateCallback(
+ (IIntrusionDetectionServiceStateCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_REMOVE_STATE_CALLBACK:
+ try {
+ mService.removeStateCallback(
+ (IIntrusionDetectionServiceStateCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_ENABLE:
+ try {
+ mService.enable((IIntrusionDetectionServiceCommandCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_DISABLE:
+ try {
+ mService.disable((IIntrusionDetectionServiceCommandCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_TRANSPORT:
+ mService.transport((List<IntrusionDetectionEvent>) msg.obj);
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+
+ private void addStateCallback(IIntrusionDetectionServiceStateCallback callback)
+ throws RemoteException {
+ for (int i = 0; i < mStateCallbacks.size(); i++) {
+ if (mStateCallbacks.get(i).asBinder() == callback.asBinder()) {
+ return;
+ }
+ }
+ mStateCallbacks.add(callback);
+ callback.onStateChange(mState);
+ }
+
+ private void removeStateCallback(IIntrusionDetectionServiceStateCallback callback)
+ throws RemoteException {
+ for (int i = 0; i < mStateCallbacks.size(); i++) {
+ if (mStateCallbacks.get(i).asBinder() == callback.asBinder()) {
+ mStateCallbacks.remove(i);
+ return;
+ }
+ }
+ }
+
+ private void notifyStateMonitors() {
+ if (mStateCallbacks.size() >= MAX_STATE_CALLBACK_NUM) {
+ mStateCallbacks.removeFirst();
+ }
+
+ for (int i = 0; i < mStateCallbacks.size(); i++) {
+ try {
+ mStateCallbacks.get(i).onStateChange(mState);
+ } catch (RemoteException e) {
+ mStateCallbacks.remove(i);
+ }
+ }
+ }
+
+ private void enable(IIntrusionDetectionServiceCommandCallback callback)
+ throws RemoteException {
+ if (mState == STATE_ENABLED) {
+ callback.onSuccess();
+ return;
+ }
+
+ if (!mIntrusionDetectionEventTransportConnection.initialize()) {
+ callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE);
+ return;
+ }
+
+ mDataAggregator.enable();
+ mState = STATE_ENABLED;
+ notifyStateMonitors();
+ callback.onSuccess();
+ }
+
+ private void disable(IIntrusionDetectionServiceCommandCallback callback)
+ throws RemoteException {
+ if (mState == STATE_DISABLED) {
+ callback.onSuccess();
+ return;
+ }
+
+ mIntrusionDetectionEventTransportConnection.release();
+ mDataAggregator.disable();
+ mState = STATE_DISABLED;
+ notifyStateMonitors();
+ callback.onSuccess();
+ }
+
+ /**
+ * Add a list of IntrusionDetectionEvent.
+ */
+ public void addNewData(List<IntrusionDetectionEvent> events) {
+ mHandler.obtainMessage(MSG_TRANSPORT, events).sendToTarget();
+ }
+
+ private void transport(List<IntrusionDetectionEvent> events) {
+ mIntrusionDetectionEventTransportConnection.addData(events);
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.INTRUSION_DETECTION_SERVICE, mBinderService);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the IntrusionDetectionService.", t);
+ }
+ }
+
+ @VisibleForTesting
+ IIntrusionDetectionService getBinderService() {
+ return mBinderService;
+ }
+
+ interface Injector {
+ Context getContext();
+
+ PermissionEnforcer getPermissionEnforcer();
+
+ Looper getLooper();
+
+ IntrusionDetectionEventTransportConnection getIntrusionDetectionEventransportConnection();
+
+ DataAggregator getDataAggregator(IntrusionDetectionService intrusionDetectionService);
+ }
+
+ private static final class InjectorImpl implements Injector {
+ private final Context mContext;
+
+ InjectorImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public PermissionEnforcer getPermissionEnforcer() {
+ return PermissionEnforcer.fromContext(mContext);
+ }
+
+ @Override
+ public Looper getLooper() {
+ ServiceThread serviceThread =
+ new ServiceThread(
+ TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ serviceThread.start();
+ return serviceThread.getLooper();
+ }
+
+ @Override
+ public IntrusionDetectionEventTransportConnection
+ getIntrusionDetectionEventransportConnection() {
+ return new IntrusionDetectionEventTransportConnection(mContext);
+ }
+
+ @Override
+ public DataAggregator getDataAggregator(
+ IntrusionDetectionService intrusionDetectionService) {
+ return new DataAggregator(mContext, intrusionDetectionService);
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
new file mode 100644
index 000000000000..083b1fd61c46
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DnsEvent;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.net.BaseNetdEventCallback;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class NetworkLogSource implements DataSource {
+
+ private static final String TAG = "IntrusionDetectionEvent NetworkLogSource";
+ private final AtomicBoolean mIsNetworkLoggingEnabled = new AtomicBoolean(false);
+ private final PackageManagerInternal mPm;
+
+ private DataAggregator mDataAggregator;
+
+ private IIpConnectivityMetrics mIpConnectivityMetrics;
+ private long mId;
+
+ public NetworkLogSource(Context context, DataAggregator dataAggregator)
+ throws SecurityException {
+ mDataAggregator = dataAggregator;
+ mPm = LocalServices.getService(PackageManagerInternal.class);
+ mId = 0;
+ initIpConnectivityMetrics();
+ }
+
+ private void initIpConnectivityMetrics() {
+ mIpConnectivityMetrics =
+ (IIpConnectivityMetrics)
+ IIpConnectivityMetrics.Stub.asInterface(
+ ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+ }
+
+ @Override
+ public void enable() {
+ if (mIsNetworkLoggingEnabled.get()) {
+ Slog.w(TAG, "Network logging is already enabled");
+ return;
+ }
+ try {
+ if (mIpConnectivityMetrics.addNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
+ mIsNetworkLoggingEnabled.set(true);
+ } else {
+ Slog.e(TAG, "Failed to enable network logging; invalid callback");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to enable network logging; ", e);
+ }
+ }
+
+ @Override
+ public void disable() {
+ if (!mIsNetworkLoggingEnabled.get()) {
+ Slog.w(TAG, "Network logging is already disabled");
+ return;
+ }
+ try {
+ if (!mIpConnectivityMetrics.removeNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST)) {
+
+ mIsNetworkLoggingEnabled.set(false);
+ } else {
+ Slog.e(TAG, "Failed to enable network logging; invalid callback");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to disable network logging; ", e);
+ }
+ }
+
+ private void incrementEventID() {
+ if (mId == Long.MAX_VALUE) {
+ Slog.i(TAG, "Reached maximum id value; wrapping around.");
+ mId = 0;
+ } else {
+ mId++;
+ }
+ }
+
+ private final INetdEventCallback mNetdEventCallback =
+ new BaseNetdEventCallback() {
+ @Override
+ public void onDnsEvent(
+ int netId,
+ int eventType,
+ int returnCode,
+ String hostname,
+ String[] ipAddresses,
+ int ipAddressesCount,
+ long timestamp,
+ int uid) {
+ if (!mIsNetworkLoggingEnabled.get()) {
+ return;
+ }
+ DnsEvent dnsEvent =
+ new DnsEvent(
+ hostname,
+ ipAddresses,
+ ipAddressesCount,
+ mPm.getNameForUid(uid),
+ timestamp);
+ dnsEvent.setId(mId);
+ incrementEventID();
+ mDataAggregator.addSingleData(new IntrusionDetectionEvent(dnsEvent));
+ }
+
+ @Override
+ public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+ if (!mIsNetworkLoggingEnabled.get()) {
+ return;
+ }
+ ConnectEvent connectEvent =
+ new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), timestamp);
+ connectEvent.setId(mId);
+ incrementEventID();
+ mDataAggregator.addSingleData(new IntrusionDetectionEvent(connectEvent));
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/OWNERS b/services/core/java/com/android/server/security/intrusiondetection/OWNERS
new file mode 100644
index 000000000000..05080670ca2c
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:main:/core/java/android/security/intrusiondetection/OWNERS
diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
new file mode 100644
index 000000000000..5611905bf270
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import android.Manifest.permission;
+import android.annotation.RequiresPermission;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class SecurityLogSource implements DataSource {
+
+ private static final String TAG = "IntrusionDetection SecurityLogSource";
+
+ private SecurityEventCallback mEventCallback;
+ private DevicePolicyManager mDpm;
+ private Executor mExecutor;
+ private DataAggregator mDataAggregator;
+
+ public SecurityLogSource(Context context, DataAggregator dataAggregator) {
+ mDataAggregator = dataAggregator;
+ mDpm = context.getSystemService(DevicePolicyManager.class);
+ mExecutor = Executors.newSingleThreadExecutor();
+ mEventCallback = new SecurityEventCallback();
+ }
+
+ @Override
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void enable() {
+ enableAuditLog();
+ mDpm.setAuditLogEventCallback(mExecutor, mEventCallback);
+ }
+
+ @Override
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void disable() {
+ disableAuditLog();
+ }
+
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ private void enableAuditLog() {
+ if (!isAuditLogEnabled()) {
+ mDpm.setAuditLogEnabled(true);
+ }
+ }
+
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ private void disableAuditLog() {
+ if (isAuditLogEnabled()) {
+ mDpm.setAuditLogEnabled(false);
+ }
+ }
+
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ private boolean isAuditLogEnabled() {
+ return mDpm.isAuditLogEnabled();
+ }
+
+ private class SecurityEventCallback implements Consumer<List<SecurityEvent>> {
+
+ @Override
+ public void accept(List<SecurityEvent> events) {
+ if (events.size() == 0) {
+ Slog.w(TAG, "No events received; caller may not be authorized");
+ return;
+ }
+ List<IntrusionDetectionEvent> intrusionDetectionEvents =
+ events.stream()
+ .filter(event -> event != null)
+ .map(event -> new IntrusionDetectionEvent(event))
+ .collect(Collectors.toList());
+ mDataAggregator.addBatchData(intrusionDetectionEvents);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
deleted file mode 100644
index 152623090314..000000000000
--- a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.servicewatcher;
-
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
-
-import android.annotation.BoolRes;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.ActivityManagerInternal;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.location.flags.Flags;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.TypedValue;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.FgThread;
-import com.android.server.LocalServices;
-import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
-import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier;
-
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Supplies services based on the current active user and version as defined in the service
- * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
- * ensure only system (ie, privileged) services are matched. It also handles services that are not
- * direct boot aware, and will automatically pick the best service as the user's direct boot state
- * changes.
- *
- * <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
- * not require callers to hold this permission is rejected (2) a service permission - any service
- * whose package does not hold this permission is rejected.
- */
-public final class CurrentUserServiceSupplier extends BroadcastReceiver implements
- ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
-
- private static final String TAG = "CurrentUserServiceSupplier";
-
- private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
- private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
-
- // a package value that will never match against any package (we can't use null since this will
- // match against any package).
- private static final String NO_MATCH_PACKAGE = "";
-
- private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
- if (o1 == o2) {
- return 0;
- } else if (o1 == null) {
- return -1;
- } else if (o2 == null) {
- return 1;
- }
-
- // ServiceInfos with higher version numbers always win. if version numbers are equal
- // then we prefer components that work for all users vs components that only work for a
- // single user at a time. otherwise everything's equal.
- int ret = Integer.compare(o1.getVersion(), o2.getVersion());
- if (ret == 0) {
- if (o1.getUserId() != USER_SYSTEM && o2.getUserId() == USER_SYSTEM) {
- ret = -1;
- } else if (o1.getUserId() == USER_SYSTEM && o2.getUserId() != USER_SYSTEM) {
- ret = 1;
- }
- }
- return ret;
- };
-
- /** Bound service information with version information. */
- public static class BoundServiceInfo extends ServiceWatcher.BoundServiceInfo {
-
- private static int parseUid(ResolveInfo resolveInfo) {
- int uid = resolveInfo.serviceInfo.applicationInfo.uid;
- Bundle metadata = resolveInfo.serviceInfo.metaData;
- if (metadata != null && metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false)) {
- // reconstruct a uid for the same app but with the system user - hope this exists
- uid = UserHandle.getUid(USER_SYSTEM, UserHandle.getAppId(uid));
- }
- return uid;
- }
-
- private static int parseVersion(ResolveInfo resolveInfo) {
- int version = Integer.MIN_VALUE;
- if (resolveInfo.serviceInfo.metaData != null) {
- version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
- }
- return version;
- }
-
- private final int mVersion;
- private final @Nullable Bundle mMetadata;
-
- protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
- this(action, parseUid(resolveInfo), resolveInfo.serviceInfo.getComponentName(),
- parseVersion(resolveInfo), resolveInfo.serviceInfo.metaData);
- }
-
- protected BoundServiceInfo(String action, int uid, ComponentName componentName, int version,
- @Nullable Bundle metadata) {
- super(action, uid, componentName);
-
- mVersion = version;
- mMetadata = metadata;
- }
-
- public int getVersion() {
- return mVersion;
- }
-
- public @Nullable Bundle getMetadata() {
- return mMetadata;
- }
-
- @Override
- public String toString() {
- return super.toString() + "@" + mVersion;
- }
- }
-
- /**
- * Creates an instance using package details retrieved from config.
- *
- * @see #create(Context, String, String, String, String)
- */
- public static CurrentUserServiceSupplier createFromConfig(Context context, String action,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- String explicitPackage = retrieveExplicitPackage(context, enableOverlayResId,
- nonOverlayPackageResId);
- return CurrentUserServiceSupplier.create(context, action, explicitPackage,
- /*callerPermission=*/null, /*servicePermission=*/null);
- }
-
- /**
- * Creates an instance with the specific service details and permission requirements.
- *
- * @param context the context the supplier is to use
- * @param action the action the service must declare in its intent-filter
- * @param explicitPackage the package of the service, or {@code null} if the package of the
- * service is not constrained
- * @param callerPermission a permission that the service forces callers (i.e.
- * ServiceWatcher/system server) to hold, or {@code null} if there isn't one
- * @param servicePermission a permission that the service package should hold, or {@code null}
- * if there isn't one
- */
- public static CurrentUserServiceSupplier create(Context context, String action,
- @Nullable String explicitPackage, @Nullable String callerPermission,
- @Nullable String servicePermission) {
- boolean matchSystemAppsOnly = true;
- return new CurrentUserServiceSupplier(context, action,
- explicitPackage, callerPermission, servicePermission, matchSystemAppsOnly);
- }
-
- /**
- * Creates an instance like {@link #create} except it allows connection to services that are not
- * supplied by system packages. Only intended for use during tests.
- *
- * @see #create(Context, String, String, String, String)
- */
- public static CurrentUserServiceSupplier createUnsafeForTestsOnly(Context context,
- String action, @Nullable String explicitPackage, @Nullable String callerPermission,
- @Nullable String servicePermission) {
- boolean matchSystemAppsOnly = false;
- return new CurrentUserServiceSupplier(context, action,
- explicitPackage, callerPermission, servicePermission, matchSystemAppsOnly);
- }
-
- private static @Nullable String retrieveExplicitPackage(Context context,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- Resources resources = context.getResources();
- boolean enableOverlay = resources.getBoolean(enableOverlayResId);
- if (!enableOverlay) {
- if (Flags.fixServiceWatcher()) {
- // we don't use getText() or similar because it won't return null values
- TypedValue out = new TypedValue();
- resources.getValue(nonOverlayPackageResId, out, true);
- CharSequence explicitPackage = out.coerceToString();
- if (explicitPackage == null) {
- return NO_MATCH_PACKAGE;
- } else {
- return explicitPackage.toString();
- }
- } else {
- return resources.getString(nonOverlayPackageResId);
- }
- } else {
- return null;
- }
- }
-
- private final Context mContext;
- private final ActivityManagerInternal mActivityManager;
- private final Intent mIntent;
- // a permission that the service forces callers (ie ServiceWatcher/system server) to hold
- private final @Nullable String mCallerPermission;
- // a permission that the service package should hold
- private final @Nullable String mServicePermission;
- // whether to use MATCH_SYSTEM_ONLY in queries
- private final boolean mMatchSystemAppsOnly;
-
- private volatile ServiceChangedListener mListener;
-
- private CurrentUserServiceSupplier(Context context, String action,
- @Nullable String explicitPackage, @Nullable String callerPermission,
- @Nullable String servicePermission, boolean matchSystemAppsOnly) {
- mContext = context;
- mActivityManager = Objects.requireNonNull(
- LocalServices.getService(ActivityManagerInternal.class));
- mIntent = new Intent(action);
-
- if (explicitPackage != null) {
- mIntent.setPackage(explicitPackage);
- }
-
- mCallerPermission = callerPermission;
- mServicePermission = servicePermission;
- mMatchSystemAppsOnly = matchSystemAppsOnly;
- }
-
- @Override
- public boolean hasMatchingService() {
- if (Flags.fixServiceWatcher() && NO_MATCH_PACKAGE.equals(mIntent.getPackage())) {
- return false;
- }
-
- int intentQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
- if (mMatchSystemAppsOnly) {
- intentQueryFlags |= MATCH_SYSTEM_ONLY;
- }
- List<ResolveInfo> resolveInfos = mContext.getPackageManager()
- .queryIntentServicesAsUser(mIntent,
- intentQueryFlags,
- UserHandle.USER_SYSTEM);
- return !resolveInfos.isEmpty();
- }
-
- @Override
- public void register(ServiceChangedListener listener) {
- Preconditions.checkState(mListener == null);
-
- mListener = listener;
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
- intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
- intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null,
- FgThread.getHandler());
- }
-
- @Override
- public void unregister() {
- Preconditions.checkArgument(mListener != null);
-
- mListener = null;
- mContext.unregisterReceiver(this);
- }
-
- @Override
- public BoundServiceInfo getServiceInfo() {
- if (Flags.fixServiceWatcher() && NO_MATCH_PACKAGE.equals(mIntent.getPackage())) {
- return null;
- }
-
- BoundServiceInfo bestServiceInfo = null;
-
- // only allow services in the correct direct boot state to match
- int intentQueryFlags = MATCH_DIRECT_BOOT_AUTO | GET_META_DATA;
- if (mMatchSystemAppsOnly) {
- intentQueryFlags |= MATCH_SYSTEM_ONLY;
- }
- int currentUserId = mActivityManager.getCurrentUserId();
- List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
- mIntent,
- intentQueryFlags,
- currentUserId);
- for (ResolveInfo resolveInfo : resolveInfos) {
- ServiceInfo service = Objects.requireNonNull(resolveInfo.serviceInfo);
-
- if (mCallerPermission != null) {
- if (!mCallerPermission.equals(service.permission)) {
- Log.d(TAG, service.getComponentName().flattenToShortString()
- + " disqualified due to not requiring " + mCallerPermission);
- continue;
- }
- }
-
- BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
-
- if (mServicePermission != null) {
- if (mContext.checkPermission(mServicePermission, Process.INVALID_PID,
- serviceInfo.mUid) != PERMISSION_GRANTED) {
- Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
- + " disqualified due to not holding " + mCallerPermission);
- continue;
- }
- }
-
- if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
- bestServiceInfo = serviceInfo;
- }
- }
-
- return bestServiceInfo;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action == null) {
- return;
- }
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) {
- return;
- }
- ServiceChangedListener listener = mListener;
- if (listener == null) {
- return;
- }
-
- switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- listener.onServiceChanged();
- break;
- case Intent.ACTION_USER_UNLOCKED:
- // user unlocked implies direct boot mode may have changed
- if (userId == mActivityManager.getCurrentUserId()) {
- listener.onServiceChanged();
- }
- break;
- default:
- break;
- }
- }
-}
diff --git a/services/core/java/com/android/server/servicewatcher/OWNERS b/services/core/java/com/android/server/servicewatcher/OWNERS
deleted file mode 100644
index ced619f05f1d..000000000000
--- a/services/core/java/com/android/server/servicewatcher/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 25692
-
-sooniln@google.com
-wyattriley@google.com
-
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
deleted file mode 100644
index 50a2b983de79..000000000000
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.servicewatcher;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-
-import com.android.server.FgThread;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-/**
- * A ServiceWatcher is responsible for continuously maintaining an active binding to a service
- * selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
- * selects over time, and the currently bound service may crash, restart, have a user change, have
- * changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
- * maintaining the binding across all these changes.
- *
- * <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
- * effort to run these on the currently bound service, but individual operations may fail (if there
- * is no service currently bound for instance). In order to help clients maintain the correct state,
- * clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
- * and disconnects from a service. This allows clients to bring a bound service back into a known
- * state on connection, and then run binder operations from there. In order to help clients
- * accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
- * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
- * can be established between them.
- *
- * There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
- * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
- * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
- * failures.
- */
-public interface ServiceWatcher {
-
- /**
- * Operation to run on a binder interface. All operations will be run on the thread used by the
- * ServiceWatcher this is run with.
- */
- interface BinderOperation {
- /** Invoked to run the operation. Run on the ServiceWatcher thread. */
- void run(IBinder binder) throws RemoteException;
-
- /**
- * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
- * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
- * {@link RuntimeException}). This callback is only intended for resource deallocation and
- * cleanup in response to a single binder operation, it should not be used to propagate
- * errors further. Run on the ServiceWatcher thread.
- */
- default void onError(Throwable t) {}
- }
-
- /**
- * Listener for bind and unbind events. All operations will be run on the thread used by the
- * ServiceWatcher this is run with.
- *
- * @param <TBoundServiceInfo> type of bound service
- */
- interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
- /** Invoked when a service is bound. Run on the ServiceWatcher thread. */
- void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
-
- /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
- void onUnbind();
- }
-
- /**
- * A listener for when a {@link ServiceSupplier} decides that the current service has changed.
- */
- interface ServiceChangedListener {
- /**
- * Should be invoked when the current service may have changed.
- */
- void onServiceChanged();
- }
-
- /**
- * This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
- * be bound to at any given moment.
- *
- * @param <TBoundServiceInfo> type of bound service
- */
- interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
- /**
- * Should return true if there exists at least one service capable of meeting the criteria
- * of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
- * non-null result, as any service may be disqualified for various reasons at any point in
- * time. May be invoked at any time from any thread and thus should generally not have any
- * dependency on the other methods in this interface.
- */
- boolean hasMatchingService();
-
- /**
- * Invoked when the supplier should start monitoring for any changes that could result in a
- * different service selection, and should invoke
- * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
- * may be invoked after this method is called.
- */
- void register(ServiceChangedListener listener);
-
- /**
- * Invoked when the supplier should stop monitoring for any changes that could result in a
- * different service selection, should no longer invoke
- * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
- * invoked after this method is called.
- */
- void unregister();
-
- /**
- * Must be implemented to return the current service selected by this supplier. May return
- * null if no service currently meets the criteria. Only invoked while registered.
- */
- @Nullable TBoundServiceInfo getServiceInfo();
- }
-
- /**
- * Information on the service selected as the best option for binding.
- */
- class BoundServiceInfo {
-
- protected final @Nullable String mAction;
- protected final int mUid;
- protected final ComponentName mComponentName;
-
- protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
- this(action, resolveInfo.serviceInfo.applicationInfo.uid,
- resolveInfo.serviceInfo.getComponentName());
- }
-
- protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
- mAction = action;
- mUid = uid;
- mComponentName = Objects.requireNonNull(componentName);
- }
-
- /** Returns the action associated with this bound service. */
- public @Nullable String getAction() {
- return mAction;
- }
-
- /** Returns the component of this bound service. */
- public ComponentName getComponentName() {
- return mComponentName;
- }
-
- /** Returns the user id for this bound service. */
- public @UserIdInt int getUserId() {
- return UserHandle.getUserId(mUid);
- }
-
- @Override
- public final boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof BoundServiceInfo)) {
- return false;
- }
-
- BoundServiceInfo that = (BoundServiceInfo) o;
- return mUid == that.mUid
- && Objects.equals(mAction, that.mAction)
- && mComponentName.equals(that.mComponentName);
- }
-
- @Override
- public final int hashCode() {
- return Objects.hash(mAction, mUid, mComponentName);
- }
-
- @Override
- public String toString() {
- return mUid + "/" + mComponentName.flattenToShortString();
- }
- }
-
- /**
- * Creates a new ServiceWatcher instance.
- */
- static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
- Context context,
- String tag,
- ServiceSupplier<TBoundServiceInfo> serviceSupplier,
- @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
- return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
- }
-
- /**
- * Creates a new ServiceWatcher instance that runs on the given handler.
- */
- static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
- Context context,
- Handler handler,
- String tag,
- ServiceSupplier<TBoundServiceInfo> serviceSupplier,
- @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
- return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
- }
-
- /**
- * Returns true if there is at least one service that the ServiceWatcher could hypothetically
- * bind to, as selected by the {@link ServiceSupplier}.
- */
- boolean checkServiceResolves();
-
- /**
- * Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
- * service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
- */
- void register();
-
- /**
- * Unregisters the ServiceWatcher, so that it will release any active bindings. If the
- * ServiceWatcher is currently bound, this will result in one final
- * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
- * (but which is guaranteed to occur before any further
- * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
- * call to {@link #register()}).
- */
- void unregister();
-
- /**
- * Runs the given binder operation on the currently bound service (if available). The operation
- * will always fail if the ServiceWatcher is not currently registered.
- */
- void runOnBinder(BinderOperation operation);
-
- /**
- * Dumps ServiceWatcher information.
- */
- void dump(PrintWriter pw);
-} \ No newline at end of file
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
deleted file mode 100644
index b1782693480a..000000000000
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.servicewatcher;
-
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-import static android.content.Context.BIND_NOT_VISIBLE;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.Preconditions;
-import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo;
-import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-/**
- * Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
- * us to store the generic relationship between the service supplier and the service listener, while
- * hiding the generics from clients, simplifying the API.
- */
-class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
- ServiceChangedListener {
-
- static final String TAG = "ServiceWatcher";
- static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
-
- static final long RETRY_DELAY_MS = 15 * 1000;
-
- final Context mContext;
- final Handler mHandler;
- final String mTag;
- final ServiceSupplier<TBoundServiceInfo> mServiceSupplier;
- final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- return true;
- }
-
- @Override
- public void onSomePackagesChanged() {
- onServiceChanged(false);
- }
- };
-
- @GuardedBy("this")
- private boolean mRegistered = false;
- @GuardedBy("this")
- private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
-
- ServiceWatcherImpl(Context context, Handler handler, String tag,
- ServiceSupplier<TBoundServiceInfo> serviceSupplier,
- ServiceListener<? super TBoundServiceInfo> serviceListener) {
- mContext = context;
- mHandler = handler;
- mTag = tag;
- mServiceSupplier = serviceSupplier;
- mServiceListener = serviceListener;
- }
-
- @Override
- public boolean checkServiceResolves() {
- return mServiceSupplier.hasMatchingService();
- }
-
- @Override
- public synchronized void register() {
- Preconditions.checkState(!mRegistered);
-
- mRegistered = true;
- mPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
- mServiceSupplier.register(this);
-
- onServiceChanged(false);
- }
-
- @Override
- public synchronized void unregister() {
- Preconditions.checkState(mRegistered);
-
- mServiceSupplier.unregister();
- mPackageMonitor.unregister();
- mRegistered = false;
-
- onServiceChanged(false);
- }
-
- @Override
- public synchronized void onServiceChanged() {
- onServiceChanged(false);
- }
-
- @Override
- public synchronized void runOnBinder(BinderOperation operation) {
- MyServiceConnection serviceConnection = mServiceConnection;
- mHandler.post(() -> serviceConnection.runOnBinder(operation));
- }
-
- synchronized void onServiceChanged(boolean forceRebind) {
- TBoundServiceInfo newBoundServiceInfo;
- if (mRegistered) {
- newBoundServiceInfo = mServiceSupplier.getServiceInfo();
- } else {
- newBoundServiceInfo = null;
- }
-
- // if the current connection is not connected, always force a rebind, helping with earlier
- // recovery when something goes wrong with a connection.
- forceRebind |= !mServiceConnection.isConnected();
-
- if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
- newBoundServiceInfo)) {
- Log.i(TAG, "[" + mTag + "] chose new implementation " + newBoundServiceInfo);
- MyServiceConnection oldServiceConnection = mServiceConnection;
- MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
- mServiceConnection = newServiceConnection;
- mHandler.post(() -> {
- oldServiceConnection.unbind();
- newServiceConnection.bind();
- });
- }
- }
-
- @Override
- public String toString() {
- MyServiceConnection serviceConnection;
- synchronized (this) {
- serviceConnection = mServiceConnection;
- }
-
- return serviceConnection.getBoundServiceInfo().toString();
- }
-
- @Override
- public void dump(PrintWriter pw) {
- MyServiceConnection serviceConnection;
- synchronized (this) {
- serviceConnection = mServiceConnection;
- }
-
- pw.println("target service=" + serviceConnection.getBoundServiceInfo());
- pw.println("connected=" + serviceConnection.isConnected());
- }
-
- // runs on the handler thread, and expects most of it's methods to be called from that thread
- private class MyServiceConnection implements ServiceConnection {
-
- private final @Nullable TBoundServiceInfo mBoundServiceInfo;
-
- // volatile so that isConnected can be called from any thread easily
- private volatile @Nullable IBinder mBinder;
- private @Nullable Runnable mRebinder;
-
- MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
- mBoundServiceInfo = boundServiceInfo;
- }
-
- // may be called from any thread
- @Nullable TBoundServiceInfo getBoundServiceInfo() {
- return mBoundServiceInfo;
- }
-
- // may be called from any thread
- boolean isConnected() {
- return mBinder != null;
- }
-
- void bind() {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBoundServiceInfo == null) {
- return;
- }
-
- if (D) {
- Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
- }
-
- mRebinder = null;
-
- Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
- mBoundServiceInfo.getComponentName());
- try {
- if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
- mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
- Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
- mRebinder = this::bind;
- mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
- }
- } catch (SecurityException e) {
- // if anything goes wrong it shouldn't crash the system server
- Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " bind failed", e);
- }
- }
-
- void unbind() {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBoundServiceInfo == null) {
- return;
- }
-
- if (D) {
- Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
- }
-
- if (mRebinder != null) {
- mHandler.removeCallbacks(mRebinder);
- mRebinder = null;
- } else {
- mContext.unbindService(this);
- }
-
- onServiceDisconnected(mBoundServiceInfo.getComponentName());
- }
-
- void runOnBinder(BinderOperation operation) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBinder == null) {
- operation.onError(new DeadObjectException());
- return;
- }
-
- try {
- operation.run(mBinder);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
- operation.onError(e);
- }
- }
-
- @Override
- public final void onServiceConnected(ComponentName component, IBinder binder) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- Preconditions.checkState(mBinder == null);
-
- Log.i(TAG, "[" + mTag + "] connected to " + component.toShortString());
-
- mBinder = binder;
-
- if (mServiceListener != null) {
- try {
- mServiceListener.onBind(binder, mBoundServiceInfo);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
- }
- }
- }
-
- @Override
- public final void onServiceDisconnected(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBinder == null) {
- return;
- }
-
- Log.i(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
-
- mBinder = null;
- if (mServiceListener != null) {
- mServiceListener.onUnbind();
- }
- }
-
- @Override
- public final void onBindingDied(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- Log.w(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
-
- // introduce a small delay to prevent spamming binding over and over, since the likely
- // cause of a binding dying is some package event that may take time to recover from
- mHandler.postDelayed(() -> onServiceChanged(true), 500);
- }
-
- @Override
- public final void onNullBinding(ComponentName component) {
- Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
- }
- }
-}
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
index f7955e86660a..40923b6009a5 100644
--- a/services/core/java/com/android/server/stats/Android.bp
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -11,3 +11,10 @@ java_aconfig_library {
name: "stats_flags_lib",
aconfig_declarations: "stats_flags",
}
+
+java_aconfig_library {
+ name: "stats_flags_lib_host",
+ aconfig_declarations: "stats_flags",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java b/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
index dcb47a7b60b6..4c9cbc449656 100644
--- a/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
+++ b/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
@@ -42,27 +42,28 @@ public class StatsBootstrapAtomService extends IStatsBootstrapAtomService.Stub {
return;
}
StatsEvent.Builder builder = StatsEvent.newBuilder().setAtomId(atom.atomId);
- for (StatsBootstrapAtomValue value : atom.values) {
+ for (StatsBootstrapAtomValue atomValue : atom.values) {
+ StatsBootstrapAtomValue.Primitive value = atomValue.value;
switch (value.getTag()) {
- case StatsBootstrapAtomValue.boolValue:
+ case StatsBootstrapAtomValue.Primitive.boolValue:
builder.writeBoolean(value.getBoolValue());
break;
- case StatsBootstrapAtomValue.intValue:
+ case StatsBootstrapAtomValue.Primitive.intValue:
builder.writeInt(value.getIntValue());
break;
- case StatsBootstrapAtomValue.longValue:
+ case StatsBootstrapAtomValue.Primitive.longValue:
builder.writeLong(value.getLongValue());
break;
- case StatsBootstrapAtomValue.floatValue:
+ case StatsBootstrapAtomValue.Primitive.floatValue:
builder.writeFloat(value.getFloatValue());
break;
- case StatsBootstrapAtomValue.stringValue:
+ case StatsBootstrapAtomValue.Primitive.stringValue:
builder.writeString(value.getStringValue());
break;
- case StatsBootstrapAtomValue.bytesValue:
+ case StatsBootstrapAtomValue.Primitive.bytesValue:
builder.writeByteArray(value.getBytesValue());
break;
- case StatsBootstrapAtomValue.stringArrayValue:
+ case StatsBootstrapAtomValue.Primitive.stringArrayValue:
builder.writeStringArray(value.getStringArrayValue());
break;
default:
@@ -71,6 +72,25 @@ public class StatsBootstrapAtomService extends IStatsBootstrapAtomService.Stub {
return;
}
+ StatsBootstrapAtomValue.Annotation[] annotations = atomValue.annotations;
+ for (StatsBootstrapAtomValue.Annotation annotation : atomValue.annotations) {
+ if (annotation.id != StatsBootstrapAtomValue.Annotation.Id.IS_UID) {
+ Slog.e(TAG, "Unexpected annotation ID: " + annotation.id
+ + ", for atom " + atom.atomId + ": only UIDs are supported!");
+ return;
+ }
+
+ switch (annotation.value.getTag()) {
+ case StatsBootstrapAtomValue.Annotation.Primitive.boolValue:
+ builder.addBooleanAnnotation(
+ annotation.id, annotation.value.getBoolValue());
+ break;
+ default:
+ Slog.e(TAG, "Unexpected value type " + annotation.value.getTag()
+ + " when logging UID for atom " + atom.atomId);
+ return;
+ }
+ }
}
StatsLog.write(builder.usePooledBuffer().build());
}
diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
deleted file mode 100644
index 6cb6dc07f8b8..000000000000
--- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.stats.pull;
-
-import static android.os.Process.PROC_OUT_STRING;
-
-import android.annotation.Nullable;
-import android.os.Process;
-import android.util.SparseArray;
-
-public final class ProcfsMemoryUtil {
- private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING };
- private static final String[] STATUS_KEYS = new String[] {
- "Uid:",
- "VmHWM:",
- "VmRSS:",
- "RssAnon:",
- "RssShmem:",
- "VmSwap:",
- };
- private static final String[] VMSTAT_KEYS = new String[] {
- "oom_kill"
- };
-
- private ProcfsMemoryUtil() {}
-
- /**
- * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
- * VmSwap, RssShmem fields in /proc/pid/status in kilobytes or null if not available.
- */
- @Nullable
- public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
- long[] output = new long[STATUS_KEYS.length];
- output[0] = -1;
- output[3] = -1;
- output[4] = -1;
- output[5] = -1;
- Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
- if (output[0] == -1 || output[3] == -1 || output[4] == -1 || output[5] == -1) {
- // Could not open or parse file.
- return null;
- }
- final MemorySnapshot snapshot = new MemorySnapshot();
- snapshot.uid = (int) output[0];
- snapshot.rssHighWaterMarkInKilobytes = (int) output[1];
- snapshot.rssInKilobytes = (int) output[2];
- snapshot.anonRssInKilobytes = (int) output[3];
- snapshot.rssShmemKilobytes = (int) output[4];
- snapshot.swapInKilobytes = (int) output[5];
- return snapshot;
- }
-
- /**
- * Reads cmdline of a process from procfs.
- *
- * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
- * if the file is not available.
- */
- public static String readCmdlineFromProcfs(int pid) {
- String[] cmdline = new String[1];
- if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) {
- return "";
- }
- return cmdline[0];
- }
-
- /**
- * Scans all /proc/pid/cmdline entries and returns a mapping between pid and cmdline.
- */
- public static SparseArray<String> getProcessCmdlines() {
- int[] pids = new int[1024];
- pids = Process.getPids("/proc", pids);
-
- SparseArray<String> cmdlines = new SparseArray<>(pids.length);
- for (int pid : pids) {
- if (pid < 0) {
- break;
- }
- String cmdline = readCmdlineFromProcfs(pid);
- if (cmdline.isEmpty()) {
- continue;
- }
- cmdlines.append(pid, cmdline);
- }
- return cmdlines;
- }
-
- public static final class MemorySnapshot {
- public int uid;
- public int rssHighWaterMarkInKilobytes;
- public int rssInKilobytes;
- public int anonRssInKilobytes;
- public int swapInKilobytes;
- public int rssShmemKilobytes;
- }
-
- /** Reads and parses selected entries of /proc/vmstat. */
- @Nullable
- static VmStat readVmStat() {
- long[] vmstat = new long[VMSTAT_KEYS.length];
- vmstat[0] = -1;
- Process.readProcLines("/proc/vmstat", VMSTAT_KEYS, vmstat);
- if (vmstat[0] == -1) {
- return null;
- }
- VmStat result = new VmStat();
- result.oomKillCount = (int) vmstat[0];
- return result;
- }
-
- static final class VmStat {
- public int oomKillCount;
- }
-}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 4b55f276026f..21e02f3f7898 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -48,6 +48,9 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import static android.util.MathUtils.constrain;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
+import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines;
+import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.internal.os.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY;
@@ -58,18 +61,21 @@ import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE;
import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_IO;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_MEMORY;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.Flags.accumulateNetworkStatsSinceBoot;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.addPressureStallInformationPuller;
import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
-import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
@@ -208,6 +214,8 @@ import com.android.internal.os.KernelSingleProcessCpuThreadReader.ProcessCpuUsag
import com.android.internal.os.LooperStats;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.ProcfsMemoryUtil;
+import com.android.internal.os.ProcfsMemoryUtil.MemorySnapshot;
import com.android.internal.os.SelectedProcessCpuThreadReader;
import com.android.internal.os.StoragedUidIoStatsReader;
import com.android.internal.util.CollectionUtils;
@@ -216,21 +224,23 @@ import com.android.role.RoleManagerLocal;
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.PinnerService.PinnedFileStats;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.MemoryStat;
import com.android.server.health.HealthServiceWrapper;
import com.android.server.notification.NotificationManagerService;
+import com.android.server.pinner.PinnerService;
+import com.android.server.pinner.PinnerService.PinnedFileStats;
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.stats.KernelWakelockReader;
import com.android.server.power.stats.KernelWakelockStats;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
-import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.stats.pull.netstats.NetworkStatsAccumulator;
import com.android.server.stats.pull.netstats.NetworkStatsExt;
import com.android.server.stats.pull.netstats.SubInfo;
+import com.android.server.stats.pull.psi.PsiData;
+import com.android.server.stats.pull.psi.PsiExtractor;
import com.android.server.storage.DiskStatsFileLogger;
import com.android.server.storage.DiskStatsLoggingService;
import com.android.server.timezonedetector.MetricsTimeZoneDetectorState;
@@ -424,6 +434,14 @@ public class StatsPullAtomService extends SystemService {
@GuardedBy("mDataBytesTransferLock")
private final ArrayList<NetworkStatsExt> mNetworkStatsBaselines = new ArrayList<>();
+ // Accumulates NetworkStats from initialization till the present moment.
+ // It is necessary to accumulate stats internally, because NetworkStats persists data for a
+ // limited amount of time, after which diff becomes incorrect without accumulation.
+ @NonNull
+ @GuardedBy("mDataBytesTransferLock")
+ private final ArrayList<NetworkStatsAccumulator> mNetworkStatsAccumulators =
+ new ArrayList<>();
+
@GuardedBy("mDataBytesTransferLock")
private long mLastNetworkStatsPollTime = -NETSTATS_POLL_RATE_LIMIT_MS;
@@ -448,6 +466,10 @@ public class StatsPullAtomService extends SystemService {
public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
addMobileBytesTransferByProcStatePuller();
+ // Whether or not to enable the new puller with pressure stall information.
+ public static final boolean ENABLE_PRESSURE_STALL_INFORMATION_PULLER =
+ addPressureStallInformationPuller();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -824,6 +846,8 @@ public class StatsPullAtomService extends SystemService {
return pullHdrCapabilities(atomTag, data);
case FrameworkStatsLog.CACHED_APPS_HIGH_WATERMARK:
return pullCachedAppsHighWatermark(atomTag, data);
+ case FrameworkStatsLog.PRESSURE_STALL_INFORMATION:
+ return pullPressureStallInformation(atomTag, data);
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
}
@@ -1034,6 +1058,9 @@ public class StatsPullAtomService extends SystemService {
registerPinnerServiceStats();
registerHdrCapabilitiesPuller();
registerCachedAppsHighWatermarkPuller();
+ if (ENABLE_PRESSURE_STALL_INFORMATION_PULLER) {
+ registerPressureStallInformation();
+ }
}
private void initMobileDataStatsPuller() {
@@ -1265,49 +1292,39 @@ public class StatsPullAtomService extends SystemService {
case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
TRANSPORT_WIFI);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
- new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+ new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
break;
}
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
TRANSPORT_WIFI);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
break;
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
final NetworkStats stats =
getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
- new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+ new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
break;
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats =
getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
break;
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{TRANSPORT_BLUETOOTH},
- /*slicedByFgbg=*/true, /*slicedByTag=*/false,
- /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{TRANSPORT_BLUETOOTH},
+ /*slicedByFgbg=*/true, /*slicedByTag=*/false,
+ /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true));
break;
}
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
@@ -1316,14 +1333,12 @@ public class StatsPullAtomService extends SystemService {
final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_MOBILE)
.setMeteredness(METERED_YES).build(), /*includeTags=*/true);
- if (wifiStats != null && cellularStats != null) {
- final NetworkStats stats = wifiStats.add(cellularStats);
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
- new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR},
- /*slicedByFgbg=*/false, /*slicedByTag=*/true,
- /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false));
- }
+ final NetworkStats stats = wifiStats.add(cellularStats);
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR},
+ /*slicedByFgbg=*/false, /*slicedByTag=*/true,
+ /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false));
break;
}
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: {
@@ -1509,12 +1524,10 @@ public class StatsPullAtomService extends SystemService {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
template, false);
final Integer transport = ruleAndTransport.second;
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false,
- /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false,
+ /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false));
}
}
@@ -1525,7 +1538,7 @@ public class StatsPullAtomService extends SystemService {
* Create a snapshot of NetworkStats for a given transport.
*/
@GuardedBy("mDataBytesTransferLock")
- @Nullable
+ @NonNull
private NetworkStats getUidNetworkStatsSnapshotForTransportLocked(int transport) {
NetworkTemplate template = null;
switch (transport) {
@@ -1564,20 +1577,49 @@ public class StatsPullAtomService extends SystemService {
* some traffic before boot.
*/
@GuardedBy("mDataBytesTransferLock")
- @Nullable
+ @NonNull
private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
@NonNull NetworkTemplate template, boolean includeTags) {
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
- final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
- final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
+ final long currentTimeMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
+ final long bootTimeMillis = currentTimeMillis - elapsedMillisSinceBoot;
+ final long bucketDurationMillis = Settings.Global.getLong(mContext.getContentResolver(),
NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);
- // Set startTime before boot so that NetworkStats includes at least one full bucket.
- // Set endTime in the future so that NetworkStats includes everything in the active bucket.
- final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
- final long endTime = currentTimeInMillis + bucketDuration;
+ if (accumulateNetworkStatsSinceBoot()) {
+ NetworkStatsAccumulator accumulator = CollectionUtils.find(
+ mNetworkStatsAccumulators, it -> it.hasEqualParameters(template, includeTags));
+ if (accumulator == null) {
+ accumulator = new NetworkStatsAccumulator(
+ template,
+ includeTags,
+ bucketDurationMillis,
+ bootTimeMillis - bucketDurationMillis);
+ mNetworkStatsAccumulators.add(accumulator);
+ }
+
+ return accumulator.queryStats(currentTimeMillis,
+ (aTemplate, aIncludeTags, aStartTime, aEndTime) -> {
+ synchronized (mDataBytesTransferLock) {
+ return getUidNetworkStatsSnapshotForTemplateLocked(aTemplate,
+ aIncludeTags, aStartTime, aEndTime);
+ }
+ });
+
+ } else {
+ // Set end time in the future to include all stats in the active bucket.
+ return getUidNetworkStatsSnapshotForTemplateLocked(
+ template, includeTags,
+ bootTimeMillis - bucketDurationMillis,
+ currentTimeMillis + bucketDurationMillis);
+ }
+ }
- // NetworkStatsManager#forceUpdate updates stats for all networks
+ @GuardedBy("mDataBytesTransferLock")
+ @NonNull
+ private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
+ @NonNull NetworkTemplate template, boolean includeTags, long startTime, long endTime) {
+ final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
if (applyNetworkStatsPollRateLimit()) {
// The new way: rate-limit force-polling for all NetworkStats queries
if (elapsedMillisSinceBoot - mLastNetworkStatsPollTime >= NETSTATS_POLL_RATE_LIMIT_MS) {
@@ -1621,12 +1663,10 @@ public class StatsPullAtomService extends SystemService {
.setMeteredness(METERED_YES).build();
final NetworkStats stats =
getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
- new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
- /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
- OEM_MANAGED_ALL, /*isTypeProxy=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
+ new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
+ /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
+ OEM_MANAGED_ALL, /*isTypeProxy=*/false));
}
return ret;
}
@@ -5132,6 +5172,55 @@ public class StatsPullAtomService extends SystemService {
);
}
+ private void registerPressureStallInformation() {
+ int tagId = FrameworkStatsLog.PRESSURE_STALL_INFORMATION;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null,
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
+ int pullPressureStallInformation(int atomTag, List<StatsEvent> pulledData) {
+ PsiExtractor psiExtractor = new PsiExtractor();
+ for (PsiData.ResourceType resourceType: PsiData.ResourceType.values()) {
+ PsiData psiData = psiExtractor.getPsiData(resourceType);
+ if (psiData == null) {
+ Slog.e(
+ TAG,
+ "Failed to pull PressureStallInformation atom for resource: "
+ + resourceType.toString());
+ continue;
+ }
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ toProtoPsiResourceType(psiData.getResourceType()),
+ psiData.getSomeAvg10SecPercentage(),
+ psiData.getSomeAvg60SecPercentage(),
+ psiData.getSomeAvg300SecPercentage(),
+ psiData.getSomeTotalUsec(),
+ psiData.getFullAvg10SecPercentage(),
+ psiData.getFullAvg60SecPercentage(),
+ psiData.getFullAvg300SecPercentage(),
+ psiData.getFullTotalUsec()));
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private int toProtoPsiResourceType(PsiData.ResourceType resourceType) {
+ if (resourceType == PsiData.ResourceType.CPU) {
+ return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU;
+ } else if (resourceType == PsiData.ResourceType.MEMORY) {
+ return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_MEMORY;
+ } else if (resourceType == PsiData.ResourceType.IO) {
+ return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_IO;
+ } else {
+ return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_UNKNOWN;
+ }
+ }
+
+
int pullSystemServerPinnerStats(int atomTag, List<StatsEvent> pulledData) {
PinnerService pinnerService = LocalServices.getService(PinnerService.class);
List<PinnedFileStats> pinnedFileStats = pinnerService.dumpDataForStatsd();
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
new file mode 100644
index 000000000000..3f7fcee5bb9a
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.netstats;
+
+import android.annotation.NonNull;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * A class that queries NetworkStats, accumulates query results, and exposes cumulative stats across
+ * the time range covered by the queries. This class is not thread-safe.
+ * <p>
+ * This is class should be used when querying NetworkStats since boot, as NetworkStats persists data
+ * only for a limited period of time.
+ *
+ * @hide
+ */
+public class NetworkStatsAccumulator {
+
+ private static final String TAG = "NetworkStatsAccumulator";
+ private final NetworkTemplate mTemplate;
+ private final boolean mWithTags;
+ private final long mBucketDurationMillis;
+ private NetworkStats mSnapshot;
+ private long mSnapshotEndTimeMillis;
+
+ public NetworkStatsAccumulator(@NonNull NetworkTemplate template, boolean withTags,
+ long bucketDurationMillis, long snapshotEndTimeMillis) {
+ mTemplate = template;
+ mWithTags = withTags;
+ mBucketDurationMillis = bucketDurationMillis;
+ mSnapshot = new NetworkStats(0, 1);
+ mSnapshotEndTimeMillis = snapshotEndTimeMillis;
+ }
+
+ /**
+ * Provides cumulative NetworkStats until given timestamp.
+ * <p>
+ * This method method may call {@code queryFunction} more than once, which includes maintaining
+ * an internal cumulative stats snapshot and querying stats after the snapshot.
+ */
+ @NonNull
+ public NetworkStats queryStats(long currentTimeMillis,
+ @NonNull StatsQueryFunction queryFunction) {
+ NetworkStats completeStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ maybeExpandSnapshot(currentTimeMillis, completeStats, queryFunction);
+ return completeStats;
+ }
+
+ /**
+ * Returns true if the accumulator is using given query parameters.
+ */
+ public boolean hasEqualParameters(@NonNull NetworkTemplate template, boolean withTags) {
+ return Objects.equals(mTemplate, template) && mWithTags == withTags;
+ }
+
+ /**
+ * Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats.
+ */
+ private void maybeExpandSnapshot(long currentTimeMillis,
+ NetworkStats completeStatsUntilCurrentTime,
+ @NonNull StatsQueryFunction queryFunction) {
+ // Update snapshot only if it is possible to expand it by at least one full bucket, and only
+ // if the new snapshot's end is not in the active bucket.
+ long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis;
+ if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
+ Log.v(TAG,
+ "Expanding snapshot (mTemplate=" + mTemplate + ", mWithTags=" + mWithTags
+ + ") from " + mSnapshotEndTimeMillis + " to " + newEndTimeMillis
+ + " at " + currentTimeMillis);
+ NetworkStats extraStats = queryFunction.queryNetworkStats(
+ mTemplate, mWithTags, mSnapshotEndTimeMillis, newEndTimeMillis);
+ mSnapshot = mSnapshot.add(extraStats);
+ mSnapshotEndTimeMillis = newEndTimeMillis;
+
+ // NetworkStats queries interpolate historical data using integers maths, which makes
+ // queries non-transitive: Query(t0, t1) + Query(t1, t2) <= Query(t0, t2).
+ // Compute interpolation data loss from moving the snapshot's end-point, and add it to
+ // the snapshot to avoid under-counting.
+ NetworkStats newStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ NetworkStats interpolationLoss = completeStatsUntilCurrentTime.subtract(newStats);
+ mSnapshot = mSnapshot.add(interpolationLoss);
+ }
+ }
+
+ /**
+ * Adds up stats in the internal cumulative snapshot and the stats that follow after it.
+ */
+ @NonNull
+ private NetworkStats snapshotPlusFollowingStats(long currentTimeMillis,
+ @NonNull StatsQueryFunction queryFunction) {
+ // Set end time in the future to include all stats in the active bucket.
+ NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
+ mSnapshotEndTimeMillis, currentTimeMillis + mBucketDurationMillis);
+ return mSnapshot.add(extraStats);
+ }
+
+ @FunctionalInterface
+ public interface StatsQueryFunction {
+ /**
+ * Returns network stats during the given time period.
+ */
+ @NonNull
+ NetworkStats queryNetworkStats(@NonNull NetworkTemplate template, boolean includeTags,
+ long startTime, long endTime);
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/psi/OWNERS b/services/core/java/com/android/server/stats/pull/psi/OWNERS
new file mode 100644
index 000000000000..f72fd7c18925
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/OWNERS
@@ -0,0 +1,9 @@
+jackrichardson@google.com
+dbrotikovskaya@google.com
+ivokay@google.com
+gagapov@google.com
+yigitfiliz@google.com
+rswang@google.com
+evaleriano@google.com
+igorstepanov@google.com
+iyou@google.com
diff --git a/services/core/java/com/android/server/stats/pull/psi/PsiData.java b/services/core/java/com/android/server/stats/pull/psi/PsiData.java
new file mode 100644
index 000000000000..d1cbf7468d19
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/PsiData.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.psi;
+
+/**
+ * Wraps PSI (Pressure Stall Information) corresponding to a system resource. See more details about
+ * PSI, see https://docs.kernel.org/accounting/psi.html#psi-pressure-stall-information.
+ */
+public class PsiData {
+ public enum ResourceType {
+ CPU,
+ MEMORY,
+ IO
+ }
+
+ static class AppsStallInfo {
+
+ /** Past 10s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+ private final float mAvg10SecPercentage;
+
+ /** Past 60s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+ private final float mAvg60SecPercentage;
+
+ /** Past 300s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+ private final float mAvg300SecPercentage;
+
+ /** Total number of microseconds that apps tasks are stalled on mResourceType.*/
+ private final long mTotalUsec;
+
+ AppsStallInfo(
+ float avg10SecPercentage, float avg60SecPercentage,
+ float avg300SecPercentage, long totalUsec) {
+ mAvg10SecPercentage = avg10SecPercentage;
+ mAvg60SecPercentage = avg60SecPercentage;
+ mAvg300SecPercentage = avg300SecPercentage;
+ mTotalUsec = totalUsec;
+ }
+ }
+
+ /** The system resource type of this {@code PsiData}. */
+ private final ResourceType mResourceType;
+
+ /** Info on some tasks are stalled on mResourceType. */
+ private final AppsStallInfo mSomeAppsStallInfo;
+
+ /**
+ * Info on all non-idle tasks are stalled on mResourceType. For the CPU ResourceType,
+ * all fields will always be 0 as it's undefined.
+ */
+ private final AppsStallInfo mFullAppsStallInfo;
+
+ PsiData(
+ ResourceType resourceType,
+ AppsStallInfo someAppsStallInfo,
+ AppsStallInfo fullAppsStallInfo) {
+ mResourceType = resourceType;
+ mSomeAppsStallInfo = someAppsStallInfo;
+ mFullAppsStallInfo = fullAppsStallInfo;
+ }
+
+ public ResourceType getResourceType() {
+ return mResourceType;
+ }
+
+ public float getSomeAvg10SecPercentage() {
+ return mSomeAppsStallInfo.mAvg10SecPercentage; }
+
+ public float getSomeAvg60SecPercentage() {
+ return mSomeAppsStallInfo.mAvg60SecPercentage; }
+
+ public float getSomeAvg300SecPercentage() {
+ return mSomeAppsStallInfo.mAvg300SecPercentage; }
+
+ public long getSomeTotalUsec() {
+ return mSomeAppsStallInfo.mTotalUsec;
+ }
+
+ public float getFullAvg10SecPercentage() {
+ return mFullAppsStallInfo.mAvg10SecPercentage;
+ }
+
+ public float getFullAvg60SecPercentage() {
+ return mFullAppsStallInfo.mAvg60SecPercentage;
+ }
+
+ public float getFullAvg300SecPercentage() {
+ return mFullAppsStallInfo.mAvg300SecPercentage; }
+
+ public long getFullTotalUsec() {
+ return mFullAppsStallInfo.mTotalUsec;
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java b/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java
new file mode 100644
index 000000000000..5d0d7e161990
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.psi;
+
+import static java.util.stream.Collectors.joining;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.MessageFormat;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PsiExtractor {
+ private static final String TAG = "PsiExtractor";
+
+ // Paths for PSI files are guarded by SELinux policy. PCS needs to be explicitly
+ // allowlisted to access these files.
+ private static final String PSI_MEMORY_PATH = "/proc/pressure/memory";
+ private static final String PSI_IO_PATH = "/proc/pressure/io";
+ private static final String PSI_CPU_PATH = "/proc/pressure/cpu";
+
+ // The patterns matching a line of PSI output such as
+ // "some avg10=0.12 avg60=0.34 avg300=0.56 total=123456" or
+ // "full avg10=0.12 avg60=0.34 avg300=0.56 total=123456" to extract the stalling percentage
+ // values for "some" and "full" line of PSI output respectively.
+ private static final String PSI_PATTERN_TEMPLATE =
+ ".*{0} avg10=(\\d+.\\d+) avg60=(\\d+.\\d+) avg300=(\\d+.\\d+) total=(\\d+).*";
+ private static final String SOME = "some";
+ private static final String FULL = "full";
+ private final PsiReader mPsiReader;
+
+ public PsiExtractor() {
+ mPsiReader = new PsiReader();
+ }
+ public PsiExtractor(PsiReader psiReader) {
+ mPsiReader = psiReader;
+ }
+
+ /**
+ * Parses /pressure/proc/{resourceType} kernel file to extract the Pressure Stall Information
+ * (PSI), more information: can be found at https://docs.kernel.org/accounting/psi.html.
+ *
+ * @param resourceType (Memory/CPU/IO) to get the PSI for.
+ */
+ @Nullable
+ public PsiData getPsiData(PsiData.ResourceType resourceType) {
+ String psiFileData;
+ if (resourceType == PsiData.ResourceType.MEMORY) {
+ psiFileData = mPsiReader.read(PSI_MEMORY_PATH);
+ } else if (resourceType == PsiData.ResourceType.IO) {
+ psiFileData = mPsiReader.read(PSI_IO_PATH);
+ } else if (resourceType == PsiData.ResourceType.CPU) {
+ psiFileData = mPsiReader.read(PSI_CPU_PATH);
+ } else {
+ Log.w(TAG, "PsiExtractor failure: cannot read kernel source file, returning null");
+ return null;
+ }
+ return parsePsiData(psiFileData, resourceType);
+ }
+
+ @Nullable
+ private static PsiData.AppsStallInfo parsePsiString(
+ String psiFileData, String appType, PsiData.ResourceType resourceType) {
+ // There is an extra case of file content: the CPU full is undefined and isn't reported for
+ // earlier versions. It should be always propagated as 0, but for the current logic purposes
+ // we will report atom only if at least one value (some/full) is presented. Thus, hardcoding
+ // the "full" line as 0 only when the "some" line is presented.
+ if (appType == FULL && resourceType == PsiData.ResourceType.CPU) {
+ if (psiFileData.contains(SOME) && !psiFileData.contains(FULL)) {
+ return new PsiData.AppsStallInfo((float) 0.0, (float) 0.0, (float) 0.0, 0);
+ }
+ }
+
+ Pattern psiStringPattern = Pattern.compile(
+ MessageFormat.format(PSI_PATTERN_TEMPLATE, appType));
+ Matcher psiLineMatcher = psiStringPattern.matcher(psiFileData);
+
+ // Parsing the line starts with "some" in the expected output.
+ // The line for "some" should always be present in PSI output. The output must be somehow
+ // malformed if the line cannot be matched.
+ if (!psiLineMatcher.find()) {
+ Log.w(TAG,
+ "Returning null: the line \"" + appType + "\" is not in expected pattern.");
+ return null;
+ }
+ try {
+ return new PsiData.AppsStallInfo(
+ Float.parseFloat(psiLineMatcher.group(1)),
+ Float.parseFloat(psiLineMatcher.group(2)),
+ Float.parseFloat(psiLineMatcher.group(3)),
+ Long.parseLong(psiLineMatcher.group(4)));
+ } catch (NumberFormatException e) {
+ Log.w(TAG,
+ "Returning null: some value in line \"" + appType
+ + "\" cannot be parsed as numeric.");
+ return null;
+ }
+ }
+
+ @Nullable
+ private static PsiData parsePsiData(
+ String psiFileData, PsiData.ResourceType resourceType) {
+ PsiData.AppsStallInfo someAppsStallInfo = parsePsiString(psiFileData, SOME, resourceType);
+ PsiData.AppsStallInfo fullAppsStallInfo = parsePsiString(psiFileData, FULL, resourceType);
+
+ if (someAppsStallInfo == null && fullAppsStallInfo == null) {
+ Log.w(TAG, "Returning empty PSI: some or full line are failed to parse");
+ return null;
+ } else if (someAppsStallInfo == null) {
+ Log.d(TAG, "Replacing some info with empty PSI record for the resource type "
+ + resourceType);
+ someAppsStallInfo = new PsiData.AppsStallInfo(
+ (float) -1.0, (float) -1.0, (float) -1.0, -1);
+ } else if (fullAppsStallInfo == null) {
+ Log.d(TAG, "Replacing full info with empty PSI record for the resource type "
+ + resourceType);
+ fullAppsStallInfo = new PsiData.AppsStallInfo(
+ (float) -1.0, (float) -1.0, (float) -1.0, -1);
+ }
+ return new PsiData(resourceType, someAppsStallInfo, fullAppsStallInfo);
+ }
+
+ /** Dependency class */
+ public static class PsiReader {
+ /**
+ * Reads file from provided path and returns its content if the file found, null otherwise.
+ *
+ * @param filePath file path to read.
+ */
+ @Nullable
+ public String read(String filePath) {
+ try (BufferedReader br =
+ new BufferedReader(new InputStreamReader(
+ new FileInputStream(filePath)))) {
+ return br.lines().collect(joining(System.lineSeparator()));
+ } catch (IOException e) {
+ Log.w(TAG, "Cannot read file " + filePath);
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index afea3038bcbb..f5f31746a09f 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -30,3 +30,19 @@ flag {
bug: "352495181"
is_fixed_read_only: true
}
+
+flag {
+ name: "accumulate_network_stats_since_boot"
+ namespace: "statsd"
+ description: "Accumulate results of NetworkStats queries to avoid hitting NetworkStats persistence limit"
+ bug: "352537247"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "add_pressure_stall_information_puller"
+ namespace: "statsd"
+ description: "Adds PressureStallInformation atom logging"
+ bug: "365731097"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index ccac96948b0a..a05e7e0acd8d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -129,6 +129,8 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -339,7 +341,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void onDisplayAdded(int displayId) {}
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
+ }
+ }
@Override
public void onDisplayRemoved(int displayId) {
@@ -1713,8 +1719,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
icons = new ArrayMap<>(mIcons);
}
synchronized (mLock) {
- // TODO(b/118592525): Currently, status bar only works on the default display.
- // Make it aware of multi-display if needed.
final UiState state = mDisplayUiState.get(DEFAULT_DISPLAY);
return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1),
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
@@ -1725,6 +1729,46 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
+ @Override
+ public Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar bar) {
+ enforceStatusBarService();
+ enforceValidCallingUser();
+
+ Slog.i(TAG, "registerStatusBarForAllDisplays bar=" + bar);
+ mBar = bar;
+ mDeathRecipient.linkToDeath();
+ notifyBarAttachChanged();
+
+ synchronized (mLock) {
+ Map<String, RegisterStatusBarResult> results = new HashMap<>();
+
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ final int displayId = mDisplayUiState.keyAt(i);
+ final UiState state = mDisplayUiState.get(displayId);
+
+ final ArrayMap<String, StatusBarIcon> icons;
+ synchronized (mIcons) {
+ icons = new ArrayMap<>(mIcons);
+ }
+
+ if (state != null) {
+ results.put(String.valueOf(displayId),
+ new RegisterStatusBarResult(icons,
+ gatherDisableActionsLocked(mCurrentUserId, 1),
+ state.mAppearance, state.mAppearanceRegions,
+ state.mImeWindowVis,
+ state.mImeBackDisposition, state.mShowImeSwitcher,
+ gatherDisableActionsLocked(mCurrentUserId, 2),
+ state.mNavbarColorManagedByIme, state.mBehavior,
+ state.mRequestedVisibleTypes,
+ state.mPackageName, state.mTransientBarTypes,
+ state.mLetterboxDetails));
+ }
+ }
+ return results;
+ }
+ }
+
private void notifyBarAttachChanged() {
UiThread.getHandler().post(() -> {
if (mGlobalActionListener == null) return;
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index 63a3e5ae7d8b..a38fc5bb6e45 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.PackageManagerInternal;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -63,7 +64,10 @@ public class TelecomLoaderService extends SystemService {
// as this loader (process="system") that's redundant here.
try {
ITelecomLoader telecomLoader = ITelecomLoader.Stub.asInterface(service);
- ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo);
+ PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo,
+ packageManagerInternal.getSystemUiServiceComponent().getPackageName());
SmsApplication.getDefaultMmsApplication(mContext, false);
ServiceManager.addService(Context.TELECOM_SERVICE, telecomService.asBinder());
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 374dd898c3a7..6405353e1f9a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -37,12 +37,14 @@ import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
import android.os.Handler;
+import android.os.SystemClock;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ApplicationSharedMemory;
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timezonedetector.ArrayMapWithHistory;
@@ -315,6 +317,17 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
// detected time if, for example, the age of all suggestions are considered.
NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get();
if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(suggestion)) {
+ if (com.android.internal.os.Flags.applicationSharedMemoryEnabled()
+ && android.os.Flags.networkTimeUsesSharedMemory()) {
+ UnixEpochTime networkUnixEpochTime = suggestion.getUnixEpochTime();
+ long lastNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis =
+ networkUnixEpochTime.getUnixEpochTimeMillis()
+ - networkUnixEpochTime.getElapsedRealtimeMillis();
+ ApplicationSharedMemory.getInstance()
+ .setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(
+ lastNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis);
+ }
+
mLastNetworkSuggestion.set(suggestion);
notifyNetworkTimeUpdateListenersAsynchronously();
}
@@ -347,8 +360,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
@Override
public synchronized void clearLatestNetworkSuggestion() {
+ if (com.android.internal.os.Flags.applicationSharedMemoryEnabled()
+ && android.os.Flags.networkTimeUsesSharedMemory()) {
+ ApplicationSharedMemory.getInstance()
+ .clearLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
+ }
mLastNetworkSuggestion.set(null);
-
notifyNetworkTimeUpdateListenersAsynchronously();
// The loss of network time may change the time signal to use to set the system clock.
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 1d2a30b56d17..708bca71eced 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1905,8 +1905,11 @@ public class TrustManagerService extends SystemService {
}
}
+ @EnforcePermission(Manifest.permission.ACCESS_FINE_LOCATION)
@Override
public boolean isInSignificantPlace() {
+ super.isInSignificantPlace_enforcePermission();
+
if (android.security.Flags.significantPlaces()) {
mSignificantPlaceServiceWatcher.runOnBinder(
binder -> ISignificantPlaceProvider.Stub.asInterface(binder)
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 91a17a9e1c31..8bcf1a9be031 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -21,6 +21,7 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
import static android.media.tv.flags.Flags.tifUnbindInactiveTis;
import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
+import static android.media.tv.flags.Flags.hdmiControlEnhancedBehavior;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,8 +45,10 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.graphics.Rect;
+import android.hardware.hdmi.HdmiClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiTvClient;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.AdBuffer;
@@ -138,6 +141,8 @@ public final class TvInputManagerService extends SystemService {
private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
"com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
+ private static final long SET_TV_AS_ACTIVE_SOURCE_IF_NO_REQUEST_DELAY_IN_MILLIS
+ = 10 * 1000; // 10 seconds
// There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
// another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -185,6 +190,8 @@ public final class TvInputManagerService extends SystemService {
private final HashSet<String> mExternalInputLoggingDeviceOnScreenDisplayNames =
new HashSet<String>();
private final List<String> mExternalInputLoggingDeviceBrandNames = new ArrayList<String>();
+ private HdmiControlManager mHdmiControlManager = null;
+ private HdmiTvClient mHdmiTvClient = null;
public TvInputManagerService(Context context) {
super(context);
@@ -197,7 +204,12 @@ public final class TvInputManagerService extends SystemService {
mActivityManager =
(ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-
+ mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
+ if (mHdmiControlManager == null) {
+ Slog.w(TAG, "HdmiControlManager is null!");
+ } else {
+ mHdmiTvClient = mHdmiControlManager.getTvClient();
+ }
synchronized (mLock) {
getOrCreateUserStateLocked(mCurrentUserId);
}
@@ -208,6 +220,42 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onStart() {
publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
+
+ if (!hdmiControlEnhancedBehavior()) {
+ return;
+ }
+
+ // To ensure the TV claims CEC active source status correctly, a receiver is registered to
+ // monitor wake-up and sleep intents. Upon wake-up, this receiver sends a delayed message
+ // triggering a TIF call into a CEC API to claim TV as the active source.
+ // However, the API call is cancelled if the TV switches inputs or goes to sleep.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ switch (action) {
+ case Intent.ACTION_SCREEN_ON:
+ Slog.w(TAG, "The TV woke up.");
+ mMessageHandler.removeMessages(
+ MessageHandler.MSG_CHECK_TV_AS_ACTIVE_SOURCE);
+ Message msg = mMessageHandler
+ .obtainMessage(MessageHandler.MSG_CHECK_TV_AS_ACTIVE_SOURCE);
+ mMessageHandler.sendMessageDelayed(msg,
+ SET_TV_AS_ACTIVE_SOURCE_IF_NO_REQUEST_DELAY_IN_MILLIS);
+ break;
+ case Intent.ACTION_SCREEN_OFF:
+ Slog.w(TAG, "The TV turned off.");
+ mMessageHandler.removeMessages(
+ MessageHandler.MSG_CHECK_TV_AS_ACTIVE_SOURCE);
+ break;
+ default:
+ return;
+ }
+ }
+ }, filter);
}
@Override
@@ -381,10 +429,12 @@ public final class TvInputManagerService extends SystemService {
// service to populate the hardware list.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
- updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.hardwareInputMap.values());
}
+ if (serviceState.needInit) {
+ updateServiceConnectionLocked(component, userId);
+ }
} else {
try {
TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -489,6 +539,27 @@ public final class TvInputManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
+ private void cleanUpHdmiDevices(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "cleanUpHdmiDevices: user " + userId);
+ }
+ UserState userState = getOrCreateUserStateLocked(userId);
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
+ try {
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceRemoved(device);
+ } else {
+ serviceState.hdmiDeviceRemovedBuffer.add(device);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
+ }
+ }
+ }
+ }
+
private void startUser(int userId) {
synchronized (mLock) {
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
@@ -500,9 +571,13 @@ public final class TvInputManagerService extends SystemService {
if (userInfo.isProfile()
&& parentInfo != null
&& parentInfo.id == mCurrentUserId) {
- // only the children of the current user can be started in background
+ int prevUserId = mCurrentUserId;
mCurrentUserId = userId;
- startProfileLocked(userId);
+ // only the children of the current user can be started in background
+ releaseSessionOfUserLocked(prevUserId);
+ cleanUpHdmiDevices(prevUserId);
+ unbindServiceOfUserLocked(prevUserId);
+ startProfileLocked(mCurrentUserId);
}
}
}
@@ -515,6 +590,7 @@ public final class TvInputManagerService extends SystemService {
}
releaseSessionOfUserLocked(userId);
+ cleanUpHdmiDevices(userId);
unbindServiceOfUserLocked(userId);
mRunningProfiles.remove(userId);
}
@@ -543,15 +619,19 @@ public final class TvInputManagerService extends SystemService {
unbindServiceOfUserLocked(runningId);
}
mRunningProfiles.clear();
- releaseSessionOfUserLocked(mCurrentUserId);
- unbindServiceOfUserLocked(mCurrentUserId);
+ int prevUserId = mCurrentUserId;
mCurrentUserId = userId;
- buildTvInputListLocked(userId, null);
- buildTvContentRatingSystemListLocked(userId);
+
+ releaseSessionOfUserLocked(prevUserId);
+ cleanUpHdmiDevices(prevUserId);
+ unbindServiceOfUserLocked(prevUserId);
+
+ buildTvInputListLocked(mCurrentUserId, null);
+ buildTvContentRatingSystemListLocked(mCurrentUserId);
mMessageHandler
.obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER,
- getContentResolverForUser(userId))
+ getContentResolverForUser(mCurrentUserId))
.sendToTarget();
}
}
@@ -590,6 +670,9 @@ public final class TvInputManagerService extends SystemService {
@GuardedBy("mLock")
private void unbindServiceOfUserLocked(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "unbindServiceOfUserLocked: user " + userId);
+ }
UserState userState = getUserStateLocked(userId);
if (userState == null) {
return;
@@ -600,7 +683,12 @@ public final class TvInputManagerService extends SystemService {
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
unbindService(serviceState);
- it.remove();
+ if (!serviceState.isHardware) {
+ it.remove();
+ } else {
+ serviceState.hardwareInputMap.clear();
+ serviceState.needInit = true;
+ }
}
}
}
@@ -774,7 +862,7 @@ public final class TvInputManagerService extends SystemService {
boolean shouldBind;
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
shouldBind = !serviceState.sessionTokens.isEmpty()
- || (serviceState.isHardware && serviceState.neverConnected);
+ || (serviceState.isHardware && serviceState.needInit);
} else {
// For a non-current user,
// if sessionTokens is not empty, it contains recording sessions only
@@ -3404,13 +3492,13 @@ public final class TvInputManagerService extends SystemService {
private ServiceCallback callback;
private boolean bound;
private boolean reconnecting;
- private boolean neverConnected;
+ private boolean needInit;
private ServiceState(ComponentName component, int userId) {
this.component = component;
this.connection = new InputServiceConnection(component, userId);
this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
- this.neverConnected = true;
+ this.needInit = true;
}
}
@@ -3618,11 +3706,9 @@ public final class TvInputManagerService extends SystemService {
}
ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent();
ServiceState serviceState = getServiceStateLocked(component, userId);
- boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
- if (removed) {
- buildTvInputListLocked(userId, null);
- mTvInputHardwareManager.removeHardwareInput(inputId);
- }
+ serviceState.hardwareInputMap.remove(inputId);
+ buildTvInputListLocked(userId, null);
+ mTvInputHardwareManager.removeHardwareInput(inputId);
}
private final class InputServiceConnection implements ServiceConnection {
@@ -3648,7 +3734,7 @@ public final class TvInputManagerService extends SystemService {
}
ServiceState serviceState = userState.serviceStateMap.get(mComponent);
serviceState.service = ITvInputService.Stub.asInterface(service);
- serviceState.neverConnected = false;
+ serviceState.needInit = false;
// Register a callback, if we need to.
if (serviceState.isHardware && serviceState.callback == null) {
@@ -3841,9 +3927,12 @@ public final class TvInputManagerService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- Slog.d(TAG, "ServiceCallback: removeHardwareInput, inputId: " + inputId +
- " by " + mComponent + ", userId: " + mUserId);
- removeHardwareInputLocked(inputId, mUserId);
+ if (mUserId == mCurrentUserId) {
+ Slog.d(TAG,
+ "ServiceCallback: removeHardwareInput, inputId: " + inputId + " by "
+ + mComponent + ", userId: " + mUserId);
+ removeHardwareInputLocked(inputId, mUserId);
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4462,6 +4551,7 @@ public final class TvInputManagerService extends SystemService {
static final int MSG_LOG_WATCH_END = 2;
static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4;
+ static final int MSG_CHECK_TV_AS_ACTIVE_SOURCE = 5;
private ContentResolver mContentResolver;
@@ -4534,8 +4624,27 @@ public final class TvInputManagerService extends SystemService {
args.recycle();
}
break;
+ case MSG_CHECK_TV_AS_ACTIVE_SOURCE:
+ synchronized (mLock) {
+ if (mOnScreenInputId == null) {
+ assertTvAsCecActiveSourceLocked();
+ break;
+ }
+ // TV that switched to a different input, but not an HDMI input
+ // (e.g. composite) can also assert active source.
+ UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
+ TvInputState inputState = userState.inputMap.get(mOnScreenInputId);
+ if (inputState == null) {
+ Slog.w(TAG, "Unexpected null TvInputState.");
+ break;
+ }
+ if (inputState.info.getType() != TvInputInfo.TYPE_HDMI) {
+ assertTvAsCecActiveSourceLocked();
+ }
+ }
+ break;
default: {
- Slog.w(TAG, "unhandled message code: " + msg.what);
+ Slog.w(TAG, "Unhandled message code: " + msg.what);
break;
}
}
@@ -4578,6 +4687,11 @@ public final class TvInputManagerService extends SystemService {
private final class HardwareListener implements TvInputHardwareManager.Listener {
@Override
public void onStateChanged(String inputId, int state) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onStateChanged: inputId " + (inputId != null ? inputId : "null")
+ + ", state " + state);
+ }
synchronized (mLock) {
setStateLocked(inputId, state, mCurrentUserId);
}
@@ -4585,6 +4699,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHardwareDeviceAdded: TvInputHardwareInfo "
+ + (info != null ? info.toString() : "null"));
+ }
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
@@ -4607,6 +4726,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHardwareDeviceRemoved: TvInputHardwareInfo "
+ + (info != null ? info.toString() : "null"));
+ }
synchronized (mLock) {
String relatedInputId =
mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId());
@@ -4634,6 +4758,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHdmiDeviceAdded: HdmiDeviceInfo "
+ + (deviceInfo != null ? deviceInfo.toString() : "null"));
+ }
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
@@ -4656,6 +4785,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHdmiDeviceRemoved: HdmiDeviceInfo "
+ + (deviceInfo != null ? deviceInfo.toString() : "null"));
+ }
synchronized (mLock) {
String relatedInputId =
mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId());
@@ -4683,6 +4817,12 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHdmiDeviceUpdated: inputId " + (inputId != null ? inputId : "null")
+ + ", deviceInfo: "
+ + (deviceInfo != null ? deviceInfo.toString() : "null"));
+ }
synchronized (mLock) {
Integer state;
switch (deviceInfo.getDevicePowerStatus()) {
@@ -4750,6 +4890,30 @@ public final class TvInputManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
+ private void assertTvAsCecActiveSourceLocked() {
+ if (mHdmiTvClient == null) {
+ Slog.w(TAG, "HdmiTvClient is null!");
+ return;
+ }
+ mHdmiTvClient.selectDevice(HdmiDeviceInfo.DEVICE_TV,
+ mContext.getMainExecutor(),
+ new HdmiClient.OnDeviceSelectedListener() {
+ @Override
+ public void onDeviceSelected(int result,
+ int logicalAddress) {
+ if (result == HdmiControlManager.RESULT_SUCCESS) {
+ Slog.w(TAG,
+ "Setting TV as the active CEC device was successful.");
+ } else {
+ Slog.w(TAG,
+ "Setting TV as the active CEC device failed with result "
+ + result);
+ }
+ }
+ });
+ }
+
private static class SessionNotFoundException extends IllegalArgumentException {
public SessionNotFoundException(String name) {
super(name);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index eb5361c84b89..d1610704a6d2 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -30,7 +30,7 @@ public class CasResource {
* Handle of the current resource. Should not be changed and should be aligned with the driver
* level implementation.
*/
- final int mHandle;
+ final long mHandle;
private final int mSystemId;
@@ -50,7 +50,7 @@ public class CasResource {
this.mAvailableSessionNum = builder.mMaxSessionNum;
}
- public int getHandle() {
+ public long getHandle() {
return mHandle;
}
@@ -146,11 +146,11 @@ public class CasResource {
*/
public static class Builder {
- private final int mHandle;
+ private final long mHandle;
private int mSystemId;
protected int mMaxSessionNum;
- Builder(int handle, int systemId) {
+ Builder(long handle, int systemId) {
this.mHandle = handle;
this.mSystemId = systemId;
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
index 5cef729627f0..b325570e35ff 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
@@ -42,7 +42,7 @@ public final class CiCamResource extends CasResource {
* Builder class for {@link CiCamResource}.
*/
public static class Builder extends CasResource.Builder {
- Builder(int handle, int systemId) {
+ Builder(long handle, int systemId) {
super(handle, systemId);
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 8e375275d080..e191ff20a518 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -67,19 +67,19 @@ public final class ClientProfile {
/**
* The handle of the primary frontend resource
*/
- private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ private long mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
/**
* List of the frontend handles that are used by the current client.
*/
- private Set<Integer> mUsingFrontendHandles = new HashSet<>();
+ private Set<Long> mUsingFrontendHandles = new HashSet<>();
/**
* List of the client ids that share frontend with the current client.
*/
private Set<Integer> mShareFeClientIds = new HashSet<>();
- private Set<Integer> mUsingDemuxHandles = new HashSet<>();
+ private Set<Long> mUsingDemuxHandles = new HashSet<>();
/**
* Client id sharee that has shared frontend with the current client.
@@ -89,7 +89,7 @@ public final class ClientProfile {
/**
* List of the Lnb handles that are used by the current client.
*/
- private Set<Integer> mUsingLnbHandles = new HashSet<>();
+ private Set<Long> mUsingLnbHandles = new HashSet<>();
/**
* List of the Cas system ids that are used by the current client.
@@ -115,11 +115,18 @@ public final class ClientProfile {
*/
private int mPriority;
+ /**
+ * If resource holder retains ownership of the resource in a challenge scenario then value is
+ * true.
+ */
+ private boolean mResourceOwnershipRetention;
+
private ClientProfile(Builder builder) {
this.mId = builder.mId;
this.mTvInputSessionId = builder.mTvInputSessionId;
this.mUseCase = builder.mUseCase;
this.mProcessId = builder.mProcessId;
+ this.mResourceOwnershipRetention = builder.mResourceOwnershipRetention;
}
public int getId() {
@@ -139,6 +146,14 @@ public final class ClientProfile {
}
/**
+ * Returns true when the resource holder retains ownership of the resource in a challenge
+ * scenario.
+ */
+ public boolean resourceOwnershipRetentionEnabled() {
+ return mResourceOwnershipRetention;
+ }
+
+ /**
* If the client priority is overwrttien.
*/
public boolean isPriorityOverwritten() {
@@ -180,11 +195,24 @@ public final class ClientProfile {
}
/**
+ * Determines whether the resource holder retains ownership of the resource during a challenge
+ * scenario, when both resource holder and resource challenger have same processId and same
+ * priority.
+ *
+ * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+ * or false to allow the resource challenger to acquire the resource.
+ * If not explicitly set, enabled is set to {@code false}.
+ */
+ public void setResourceOwnershipRetention(boolean enabled) {
+ mResourceOwnershipRetention = enabled;
+ }
+
+ /**
* Set when the client starts to use a frontend.
*
* @param frontendHandle being used.
*/
- public void useFrontend(int frontendHandle) {
+ public void useFrontend(long frontendHandle) {
mUsingFrontendHandles.add(frontendHandle);
}
@@ -193,14 +221,14 @@ public final class ClientProfile {
*
* @param frontendHandle being used.
*/
- public void setPrimaryFrontend(int frontendHandle) {
+ public void setPrimaryFrontend(long frontendHandle) {
mPrimaryUsingFrontendHandle = frontendHandle;
}
/**
* Get the primary frontend used by the client
*/
- public int getPrimaryFrontend() {
+ public long getPrimaryFrontend() {
return mPrimaryUsingFrontendHandle;
}
@@ -222,7 +250,7 @@ public final class ClientProfile {
mShareFeClientIds.remove(clientId);
}
- public Set<Integer> getInUseFrontendHandles() {
+ public Set<Long> getInUseFrontendHandles() {
return mUsingFrontendHandles;
}
@@ -253,14 +281,14 @@ public final class ClientProfile {
*
* @param demuxHandle the demux being used.
*/
- public void useDemux(int demuxHandle) {
+ public void useDemux(long demuxHandle) {
mUsingDemuxHandles.add(demuxHandle);
}
/**
* Get the set of demux handles in use.
*/
- public Set<Integer> getInUseDemuxHandles() {
+ public Set<Long> getInUseDemuxHandles() {
return mUsingDemuxHandles;
}
@@ -269,7 +297,7 @@ public final class ClientProfile {
*
* @param demuxHandle the demux handl being released.
*/
- public void releaseDemux(int demuxHandle) {
+ public void releaseDemux(long demuxHandle) {
mUsingDemuxHandles.remove(demuxHandle);
}
@@ -278,11 +306,11 @@ public final class ClientProfile {
*
* @param lnbHandle being used.
*/
- public void useLnb(int lnbHandle) {
+ public void useLnb(long lnbHandle) {
mUsingLnbHandles.add(lnbHandle);
}
- public Set<Integer> getInUseLnbHandles() {
+ public Set<Long> getInUseLnbHandles() {
return mUsingLnbHandles;
}
@@ -291,7 +319,7 @@ public final class ClientProfile {
*
* @param lnbHandle being released.
*/
- public void releaseLnb(int lnbHandle) {
+ public void releaseLnb(long lnbHandle) {
mUsingLnbHandles.remove(lnbHandle);
}
@@ -361,6 +389,7 @@ public final class ClientProfile {
private String mTvInputSessionId;
private int mUseCase;
private int mProcessId;
+ private boolean mResourceOwnershipRetention = false;
Builder(int id) {
this.mId = id;
@@ -397,6 +426,18 @@ public final class ClientProfile {
}
/**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param enabled the determining factor for resource ownership during challenger scenario.
+ * The default behavior favors the resource challenger and grants them ownership of
+ * the resource if resourceOwnershipRetention is not explicitly set to true.
+ */
+ public Builder resourceOwnershipRetention(boolean enabled) {
+ this.mResourceOwnershipRetention = enabled;
+ return this;
+ }
+
+ /**
* Build a {@link ClientProfile}.
*
* @return {@link ClientProfile}.
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
index df735659c0fe..14bc216d03ef 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
@@ -69,7 +69,7 @@ public final class DemuxResource extends TunerResourceBasic {
public static class Builder extends TunerResourceBasic.Builder {
private int mFilterTypes;
- Builder(int handle) {
+ Builder(long handle) {
super(handle);
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
index 7ef75e3120c5..953d97499c41 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
@@ -42,7 +42,7 @@ public final class FrontendResource extends TunerResourceBasic {
/**
* An array to save all the FE handles under the same exclisive group.
*/
- private Set<Integer> mExclusiveGroupMemberHandles = new HashSet<>();
+ private Set<Long> mExclusiveGroupMemberHandles = new HashSet<>();
private FrontendResource(Builder builder) {
super(builder);
@@ -58,7 +58,7 @@ public final class FrontendResource extends TunerResourceBasic {
return mExclusiveGroupId;
}
- public Set<Integer> getExclusiveGroupMemberFeHandles() {
+ public Set<Long> getExclusiveGroupMemberFeHandles() {
return mExclusiveGroupMemberHandles;
}
@@ -67,7 +67,7 @@ public final class FrontendResource extends TunerResourceBasic {
*
* @param handle the handle to be added.
*/
- public void addExclusiveGroupMemberFeHandle(int handle) {
+ public void addExclusiveGroupMemberFeHandle(long handle) {
mExclusiveGroupMemberHandles.add(handle);
}
@@ -76,7 +76,7 @@ public final class FrontendResource extends TunerResourceBasic {
*
* @param handles the handle collection to be added.
*/
- public void addExclusiveGroupMemberFeHandles(Collection<Integer> handles) {
+ public void addExclusiveGroupMemberFeHandles(Collection<Long> handles) {
mExclusiveGroupMemberHandles.addAll(handles);
}
@@ -85,7 +85,7 @@ public final class FrontendResource extends TunerResourceBasic {
*
* @param id the id to be removed.
*/
- public void removeExclusiveGroupMemberFeId(int handle) {
+ public void removeExclusiveGroupMemberFeId(long handle) {
mExclusiveGroupMemberHandles.remove(handle);
}
@@ -104,7 +104,7 @@ public final class FrontendResource extends TunerResourceBasic {
@Type private int mType;
private int mExclusiveGroupId;
- Builder(int handle) {
+ Builder(long handle) {
super(handle);
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
index 41cacea5f09e..ab283713b15a 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
@@ -37,8 +37,7 @@ public final class LnbResource extends TunerResourceBasic {
* Builder class for {@link LnbResource}.
*/
public static class Builder extends TunerResourceBasic.Builder {
-
- Builder(int handle) {
+ Builder(long handle) {
super(handle);
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
index 07853fc69055..d2ff8fa7c2e8 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
@@ -28,7 +28,7 @@ public class TunerResourceBasic {
* Handle of the current resource. Should not be changed and should be aligned with the driver
* level implementation.
*/
- final int mHandle;
+ final long mHandle;
/**
* If the current resource is in use.
@@ -44,7 +44,7 @@ public class TunerResourceBasic {
this.mHandle = builder.mHandle;
}
- public int getHandle() {
+ public long getHandle() {
return mHandle;
}
@@ -78,9 +78,9 @@ public class TunerResourceBasic {
* Builder class for {@link TunerResourceBasic}.
*/
public static class Builder {
- private final int mHandle;
+ private final long mHandle;
- Builder(int handle) {
+ Builder(long handle) {
this.mHandle = handle;
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 9229f7f016bc..bb192c0b4603 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.tv.tunerresourcemanager;
+import static android.media.tv.flags.Flags.setResourceHolderRetain;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -78,12 +80,18 @@ public class TunerResourceManagerService extends SystemService implements IBinde
private static final int INVALID_FE_COUNT = -1;
+ private static final int RESOURCE_ID_SHIFT = 24;
+ private static final int RESOURCE_TYPE_SHIFT = 56;
+ private static final long RESOURCE_COUNT_MASK = 0xffffff;
+ private static final long RESOURCE_ID_MASK = 0xffffffff;
+ private static final long RESOURCE_TYPE_MASK = 0xff;
+
// Map of the registered client profiles
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
private int mNextUnusedClientId = 0;
// Map of the current available frontend resources
- private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
+ private Map<Long, FrontendResource> mFrontendResources = new HashMap<>();
// SparseIntArray of the max usable number for each frontend resource type
private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray();
// SparseIntArray of the currently used number for each frontend resource type
@@ -93,15 +101,15 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// Backups for the frontend resource maps for enabling testing with custom resource maps
// such as TunerTest.testHasUnusedFrontend1()
- private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
+ private Map<Long, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray();
private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray();
private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray();
// Map of the current available demux resources
- private Map<Integer, DemuxResource> mDemuxResources = new HashMap<>();
+ private Map<Long, DemuxResource> mDemuxResources = new HashMap<>();
// Map of the current available lnb resources
- private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
+ private Map<Long, LnbResource> mLnbResources = new HashMap<>();
// Map of the current available Cas resources
private Map<Integer, CasResource> mCasResources = new HashMap<>();
// Map of the current available CiCam resources
@@ -223,6 +231,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
+ public void setResourceOwnershipRetention(int clientId, boolean enabled) {
+ enforceTrmAccessPermission("setResourceOwnershipRetention");
+ synchronized (mLock) {
+ getClientProfile(clientId).setResourceOwnershipRetention(enabled);
+ }
+ }
+
+ @Override
public boolean isLowestPriority(int clientId, int frontendType)
throws RemoteException {
enforceTrmAccessPermission("isLowestPriority");
@@ -266,7 +282,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void setLnbInfoList(int[] lnbHandles) throws RemoteException {
+ public void setLnbInfoList(long[] lnbHandles) throws RemoteException {
enforceTrmAccessPermission("setLnbInfoList");
if (lnbHandles == null) {
throw new RemoteException("Lnb handle list can't be null");
@@ -277,8 +293,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public boolean requestFrontend(@NonNull TunerFrontendRequest request,
- @NonNull int[] frontendHandle) {
+ public boolean requestFrontend(
+ @NonNull TunerFrontendRequest request, @NonNull long[] frontendHandle) {
enforceTunerAccessPermission("requestFrontend");
enforceTrmAccessPermission("requestFrontend");
if (frontendHandle == null) {
@@ -350,8 +366,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public boolean requestDemux(@NonNull TunerDemuxRequest request,
- @NonNull int[] demuxHandle) throws RemoteException {
+ public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull long[] demuxHandle)
+ throws RemoteException {
enforceTunerAccessPermission("requestDemux");
enforceTrmAccessPermission("requestDemux");
if (demuxHandle == null) {
@@ -362,7 +378,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
@Override
public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
- @NonNull int[] descramblerHandle) throws RemoteException {
+ @NonNull long[] descramblerHandle) throws RemoteException {
enforceDescramblerAccessPermission("requestDescrambler");
enforceTrmAccessPermission("requestDescrambler");
if (descramblerHandle == null) {
@@ -379,7 +395,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
@Override
public boolean requestCasSession(@NonNull CasSessionRequest request,
- @NonNull int[] casSessionHandle) throws RemoteException {
+ @NonNull long[] casSessionHandle) throws RemoteException {
enforceTrmAccessPermission("requestCasSession");
if (casSessionHandle == null) {
throw new RemoteException("casSessionHandle can't be null");
@@ -388,8 +404,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public boolean requestCiCam(@NonNull TunerCiCamRequest request,
- @NonNull int[] ciCamHandle) throws RemoteException {
+ public boolean requestCiCam(@NonNull TunerCiCamRequest request, @NonNull long[] ciCamHandle)
+ throws RemoteException {
enforceTrmAccessPermission("requestCiCam");
if (ciCamHandle == null) {
throw new RemoteException("ciCamHandle can't be null");
@@ -398,7 +414,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle)
+ public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull long[] lnbHandle)
throws RemoteException {
enforceTunerAccessPermission("requestLnb");
enforceTrmAccessPermission("requestLnb");
@@ -409,14 +425,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
+ public void releaseFrontend(long frontendHandle, int clientId) throws RemoteException {
enforceTunerAccessPermission("releaseFrontend");
enforceTrmAccessPermission("releaseFrontend");
releaseFrontendInternal(frontendHandle, clientId);
}
@Override
- public void releaseDemux(int demuxHandle, int clientId) throws RemoteException {
+ public void releaseDemux(long demuxHandle, int clientId) throws RemoteException {
enforceTunerAccessPermission("releaseDemux");
enforceTrmAccessPermission("releaseDemux");
if (DEBUG) {
@@ -447,7 +463,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void releaseDescrambler(int descramblerHandle, int clientId) {
+ public void releaseDescrambler(long descramblerHandle, int clientId) {
enforceTunerAccessPermission("releaseDescrambler");
enforceTrmAccessPermission("releaseDescrambler");
if (DEBUG) {
@@ -456,7 +472,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
+ public void releaseCasSession(long casSessionHandle, int clientId) throws RemoteException {
enforceTrmAccessPermission("releaseCasSession");
if (!validateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
@@ -480,7 +496,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void releaseCiCam(int ciCamHandle, int clientId) throws RemoteException {
+ public void releaseCiCam(long ciCamHandle, int clientId) throws RemoteException {
enforceTrmAccessPermission("releaseCiCam");
if (!validateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCamHandle)) {
@@ -508,7 +524,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void releaseLnb(int lnbHandle, int clientId) throws RemoteException {
+ public void releaseLnb(long lnbHandle, int clientId) throws RemoteException {
enforceTunerAccessPermission("releaseLnb");
enforceTrmAccessPermission("releaseLnb");
if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
@@ -812,7 +828,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// A set to record the frontends pending on updating. Ids will be removed
// from this set once its updating finished. Any frontend left in this set when all
// the updates are done will be removed from mFrontendResources.
- Set<Integer> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());
+ Set<Long> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());
// Update frontendResources map and other mappings accordingly
for (int i = 0; i < infos.length; i++) {
@@ -831,7 +847,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
}
- for (int removingHandle : updatingFrontendHandles) {
+ for (long removingHandle : updatingFrontendHandles) {
// update the exclusive group id member list
removeFrontendResource(removingHandle);
}
@@ -849,7 +865,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// A set to record the demuxes pending on updating. Ids will be removed
// from this set once its updating finished. Any demux left in this set when all
// the updates are done will be removed from mDemuxResources.
- Set<Integer> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
+ Set<Long> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
// Update demuxResources map and other mappings accordingly
for (int i = 0; i < infos.length; i++) {
@@ -867,13 +883,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
}
- for (int removingHandle : updatingDemuxHandles) {
+ for (long removingHandle : updatingDemuxHandles) {
// update the exclusive group id member list
removeDemuxResource(removingHandle);
}
}
@VisibleForTesting
- protected void setLnbInfoListInternal(int[] lnbHandles) {
+ protected void setLnbInfoListInternal(long[] lnbHandles) {
if (DEBUG) {
for (int i = 0; i < lnbHandles.length; i++) {
Slog.d(TAG, "updateLnbInfo(lnbHanle=" + lnbHandles[i] + ")");
@@ -883,7 +899,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// A set to record the Lnbs pending on updating. Handles will be removed
// from this set once its updating finished. Any lnb left in this set when all
// the updates are done will be removed from mLnbResources.
- Set<Integer> updatingLnbHandles = new HashSet<>(getLnbResources().keySet());
+ Set<Long> updatingLnbHandles = new HashSet<>(getLnbResources().keySet());
// Update lnbResources map and other mappings accordingly
for (int i = 0; i < lnbHandles.length; i++) {
@@ -899,7 +915,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
}
- for (int removingHandle : updatingLnbHandles) {
+ for (long removingHandle : updatingLnbHandles) {
removeLnbResource(removingHandle);
}
}
@@ -933,12 +949,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return;
}
// Add the new Cas Resource.
- int casSessionHandle = generateResourceHandle(
+ long casSessionHandle = generateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSystemId);
cas = new CasResource.Builder(casSessionHandle, casSystemId)
.maxSessionNum(maxSessionNum)
.build();
- int ciCamHandle = generateResourceHandle(
+ long ciCamHandle = generateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, casSystemId);
ciCam = new CiCamResource.Builder(ciCamHandle, casSystemId)
.maxSessionNum(maxSessionNum)
@@ -948,7 +964,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@VisibleForTesting
- protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
+ protected boolean requestFrontendInternal(TunerFrontendRequest request, long[] frontendHandle) {
if (DEBUG) {
Slog.d(TAG, "requestFrontend(request=" + request + ")");
}
@@ -977,7 +993,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
protected boolean claimFrontend(
TunerFrontendRequest request,
- int[] frontendHandle,
+ long[] frontendHandle,
int[] reclaimOwnerId
) {
frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
@@ -1032,7 +1048,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// currently in primary use (and simply blocked due to exclusive group)
ClientProfile targetOwnerProfile =
getClientProfile(fr.getOwnerClientId());
- int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+ long primaryFeId = targetOwnerProfile.getPrimaryFrontend();
FrontendResource primaryFe = getFrontendResource(primaryFeId);
if (fr.getType() != primaryFe.getType()
&& isFrontendMaxNumUseReached(fr.getType())) {
@@ -1060,8 +1076,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// request client has higher priority.
if (inUseLowestPriorityFrontend != null
&& ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
return true;
@@ -1081,7 +1100,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
getClientProfile(shareeFeClientId).stopSharingFrontend(selfClientId);
getClientProfile(selfClientId).releaseFrontend();
}
- for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
+ for (long feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
getClientProfile(selfClientId).useFrontend(feId);
}
getClientProfile(selfClientId).setShareeFeClientId(targetClientId);
@@ -1096,14 +1115,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde
currentOwnerProfile.stopSharingFrontend(newOwnerId);
newOwnerProfile.setShareeFeClientId(ClientProfile.INVALID_RESOURCE_ID);
currentOwnerProfile.setShareeFeClientId(newOwnerId);
- for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
+ for (long inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
getFrontendResource(inUseHandle).setOwner(newOwnerId);
}
// change the primary frontend
newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend());
currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE);
// double check there is no other resources tied to the previous owner
- for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
+ for (long inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
int ownerId = getFrontendResource(inUseHandle).getOwnerClientId();
if (ownerId != newOwnerId) {
Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle
@@ -1135,8 +1154,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
- Set<Integer> inUseLnbHandles = new HashSet<>();
- for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
+ Set<Long> inUseLnbHandles = new HashSet<>();
+ for (Long lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
// link lnb handle to the new profile
newOwnerProfile.useLnb(lnbHandle);
@@ -1148,7 +1167,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
// unlink lnb handles from the original owner
- for (Integer lnbHandle : inUseLnbHandles) {
+ for (Long lnbHandle : inUseLnbHandles) {
currentOwnerProfile.releaseLnb(lnbHandle);
}
@@ -1171,7 +1190,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@VisibleForTesting
- protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
+ protected boolean requestLnbInternal(TunerLnbRequest request, long[] lnbHandle)
throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestLnb(request=" + request + ")");
@@ -1199,7 +1218,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return true;
}
- protected boolean claimLnb(TunerLnbRequest request, int[] lnbHandle, int[] reclaimOwnerId)
+ protected boolean claimLnb(TunerLnbRequest request, long[] lnbHandle, int[] reclaimOwnerId)
throws RemoteException {
lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
reclaimOwnerId[0] = INVALID_CLIENT_ID;
@@ -1243,9 +1262,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityLnb != null
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
return true;
@@ -1256,7 +1278,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@VisibleForTesting
- protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle)
+ protected boolean requestCasSessionInternal(CasSessionRequest request, long[] casSessionHandle)
throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestCasSession(request=" + request + ")");
@@ -1284,7 +1306,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return true;
}
- protected boolean claimCasSession(CasSessionRequest request, int[] casSessionHandle,
+ protected boolean claimCasSession(CasSessionRequest request, long[] casSessionHandle,
int[] reclaimOwnerId) throws RemoteException {
casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
reclaimOwnerId[0] = INVALID_CLIENT_ID;
@@ -1296,7 +1318,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
CasResource cas = getCasResource(request.casSystemId);
// Unregistered Cas System is treated as having unlimited sessions.
if (cas == null) {
- int resourceHandle = generateResourceHandle(
+ long resourceHandle = generateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, request.clientId);
cas = new CasResource.Builder(resourceHandle, request.casSystemId)
.maxSessionNum(Integer.MAX_VALUE)
@@ -1329,8 +1351,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// request client has higher priority.
if (lowestPriorityOwnerId != INVALID_CLIENT_ID
&& ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
casSessionHandle[0] = cas.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1341,7 +1366,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@VisibleForTesting
- protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle)
+ protected boolean requestCiCamInternal(TunerCiCamRequest request, long[] ciCamHandle)
throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
@@ -1369,7 +1394,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return true;
}
- protected boolean claimCiCam(TunerCiCamRequest request, int[] ciCamHandle,
+ protected boolean claimCiCam(TunerCiCamRequest request, long[] ciCamHandle,
int[] reclaimOwnerId) throws RemoteException {
ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
reclaimOwnerId[0] = INVALID_CLIENT_ID;
@@ -1381,7 +1406,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
CiCamResource ciCam = getCiCamResource(request.ciCamId);
// Unregistered CiCam is treated as having unlimited sessions.
if (ciCam == null) {
- int resourceHandle = generateResourceHandle(
+ long resourceHandle = generateResourceHandle(
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, request.ciCamId);
ciCam = new CiCamResource.Builder(resourceHandle, request.ciCamId)
.maxSessionNum(Integer.MAX_VALUE)
@@ -1414,8 +1439,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// request client has higher priority.
if (lowestPriorityOwnerId != INVALID_CLIENT_ID
&& ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
ciCamHandle[0] = ciCam.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1454,7 +1482,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@VisibleForTesting
- protected void releaseFrontendInternal(int frontendHandle, int clientId)
+ protected void releaseFrontendInternal(long frontendHandle, int clientId)
throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "releaseFrontend(id=" + frontendHandle + ", clientId=" + clientId + " )");
@@ -1475,7 +1503,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
}
- private Set<Integer> unclaimFrontend(int frontendHandle, int clientId) throws RemoteException {
+ private Set<Integer> unclaimFrontend(long frontendHandle, int clientId) throws RemoteException {
Set<Integer> reclaimedResourceOwnerIds = null;
synchronized (mLock) {
if (!checkClientExists(clientId)) {
@@ -1533,7 +1561,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
@VisibleForTesting
public boolean requestDemuxInternal(@NonNull TunerDemuxRequest request,
- @NonNull int[] demuxHandle) throws RemoteException {
+ @NonNull long[] demuxHandle) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "requestDemux(request=" + request + ")");
}
@@ -1560,7 +1588,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return true;
}
- protected boolean claimDemux(TunerDemuxRequest request, int[] demuxHandle, int[] reclaimOwnerId)
+ protected boolean claimDemux(
+ TunerDemuxRequest request, long[] demuxHandle, int[] reclaimOwnerId)
throws RemoteException {
demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
reclaimOwnerId[0] = INVALID_CLIENT_ID;
@@ -1648,9 +1677,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityDemux != null
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
return true;
@@ -1676,7 +1708,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
@VisibleForTesting
protected boolean requestDescramblerInternal(
- TunerDescramblerRequest request, int[] descramblerHandle) {
+ TunerDescramblerRequest request, long[] descramblerHandle) {
if (DEBUG) {
Slog.d(TAG, "requestDescrambler(request=" + request + ")");
}
@@ -2008,20 +2040,20 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return false;
}
- private void updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
+ private void updateFrontendClientMappingOnNewGrant(long grantingHandle, int ownerClientId) {
FrontendResource grantingFrontend = getFrontendResource(grantingHandle);
ClientProfile ownerProfile = getClientProfile(ownerClientId);
grantingFrontend.setOwner(ownerClientId);
increFrontendNum(mFrontendUsedNums, grantingFrontend.getType());
ownerProfile.useFrontend(grantingHandle);
- for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
+ for (long exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
ownerProfile.useFrontend(exclusiveGroupMember);
}
ownerProfile.setPrimaryFrontend(grantingHandle);
}
- private void updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
+ private void updateDemuxClientMappingOnNewGrant(long grantingHandle, int ownerClientId) {
DemuxResource grantingDemux = getDemuxResource(grantingHandle);
if (grantingDemux != null) {
ClientProfile ownerProfile = getClientProfile(ownerClientId);
@@ -2036,7 +2068,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
ownerProfile.releaseDemux(releasingDemux.getHandle());
}
- private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
+ private void updateLnbClientMappingOnNewGrant(long grantingHandle, int ownerClientId) {
LnbResource grantingLnb = getLnbResource(grantingHandle);
ClientProfile ownerProfile = getClientProfile(ownerClientId);
grantingLnb.setOwner(ownerClientId);
@@ -2120,23 +2152,23 @@ public class TunerResourceManagerService extends SystemService implements IBinde
@VisibleForTesting
@Nullable
- protected FrontendResource getFrontendResource(int frontendHandle) {
+ protected FrontendResource getFrontendResource(long frontendHandle) {
return mFrontendResources.get(frontendHandle);
}
@VisibleForTesting
- protected Map<Integer, FrontendResource> getFrontendResources() {
+ protected Map<Long, FrontendResource> getFrontendResources() {
return mFrontendResources;
}
@VisibleForTesting
@Nullable
- protected DemuxResource getDemuxResource(int demuxHandle) {
+ protected DemuxResource getDemuxResource(long demuxHandle) {
return mDemuxResources.get(demuxHandle);
}
@VisibleForTesting
- protected Map<Integer, DemuxResource> getDemuxResources() {
+ protected Map<Long, DemuxResource> getDemuxResources() {
return mDemuxResources;
}
@@ -2195,8 +2227,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
}
- private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer,
- FrontendResource> dstMap) {
+ private void replaceFeResourceMap(
+ Map<Long, FrontendResource> srcMap, Map<Long, FrontendResource> dstMap) {
if (dstMap != null) {
dstMap.clear();
if (srcMap != null && srcMap.size() > 0) {
@@ -2249,7 +2281,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
newFe.addExclusiveGroupMemberFeHandle(fe.getHandle());
newFe.addExclusiveGroupMemberFeHandles(fe.getExclusiveGroupMemberFeHandles());
- for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
+ for (long excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
getFrontendResource(excGroupmemberFeHandle)
.addExclusiveGroupMemberFeHandle(newFe.getHandle());
}
@@ -2267,7 +2299,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
mDemuxResources.put(newDemux.getHandle(), newDemux);
}
- private void removeFrontendResource(int removingHandle) {
+ private void removeFrontendResource(long removingHandle) {
FrontendResource fe = getFrontendResource(removingHandle);
if (fe == null) {
return;
@@ -2279,7 +2311,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
clearFrontendAndClientMapping(ownerClient);
}
- for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
+ for (long excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
getFrontendResource(excGroupmemberFeHandle)
.removeExclusiveGroupMemberFeId(fe.getHandle());
}
@@ -2287,7 +2319,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
mFrontendResources.remove(removingHandle);
}
- private void removeDemuxResource(int removingHandle) {
+ private void removeDemuxResource(long removingHandle) {
DemuxResource demux = getDemuxResource(removingHandle);
if (demux == null) {
return;
@@ -2300,12 +2332,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde
@VisibleForTesting
@Nullable
- protected LnbResource getLnbResource(int lnbHandle) {
+ protected LnbResource getLnbResource(long lnbHandle) {
return mLnbResources.get(lnbHandle);
}
@VisibleForTesting
- protected Map<Integer, LnbResource> getLnbResources() {
+ protected Map<Long, LnbResource> getLnbResources() {
return mLnbResources;
}
@@ -2314,7 +2346,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
mLnbResources.put(newLnb.getHandle(), newLnb);
}
- private void removeLnbResource(int removingHandle) {
+ private void removeLnbResource(long removingHandle) {
LnbResource lnb = getLnbResource(removingHandle);
if (lnb == null) {
return;
@@ -2416,7 +2448,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
if (profile == null) {
return;
}
- for (Integer feId : profile.getInUseFrontendHandles()) {
+ for (Long feId : profile.getInUseFrontendHandles()) {
FrontendResource fe = getFrontendResource(feId);
int ownerClientId = fe.getOwnerClientId();
if (ownerClientId == profile.getId()) {
@@ -2427,10 +2459,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde
if (ownerClientProfile != null) {
ownerClientProfile.stopSharingFrontend(profile.getId());
}
-
}
- int primaryFeId = profile.getPrimaryFrontend();
+ long primaryFeId = profile.getPrimaryFrontend();
if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
FrontendResource primaryFe = getFrontendResource(primaryFeId);
if (primaryFe != null) {
@@ -2448,7 +2479,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return;
}
// Clear Lnb
- for (Integer lnbHandle : profile.getInUseLnbHandles()) {
+ for (Long lnbHandle : profile.getInUseLnbHandles()) {
getLnbResource(lnbHandle).removeOwner();
}
// Clear Cas
@@ -2460,7 +2491,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId());
}
// Clear Demux
- for (Integer demuxHandle : profile.getInUseDemuxHandles()) {
+ for (Long demuxHandle : profile.getInUseDemuxHandles()) {
getDemuxResource(demuxHandle).removeOwner();
}
// Clear Frontend
@@ -2473,24 +2504,31 @@ public class TunerResourceManagerService extends SystemService implements IBinde
return mClientProfiles.keySet().contains(clientId);
}
- private int generateResourceHandle(
+ /**
+ * Generate resource handle for resourceType and resourceId
+ * Resource Handle Allotment : 64 bits (long)
+ * 8 bits - resourceType
+ * 32 bits - resourceId
+ * 24 bits - resourceRequestCount
+ */
+ private long generateResourceHandle(
@TunerResourceManager.TunerResourceType int resourceType, int resourceId) {
- return (resourceType & 0x000000ff) << 24
- | (resourceId << 16)
- | (mResourceRequestCount++ & 0xffff);
+ return (resourceType & RESOURCE_TYPE_MASK) << RESOURCE_TYPE_SHIFT
+ | (resourceId & RESOURCE_ID_MASK) << RESOURCE_ID_SHIFT
+ | (mResourceRequestCount++ & RESOURCE_COUNT_MASK);
}
@VisibleForTesting
- protected int getResourceIdFromHandle(int resourceHandle) {
+ protected int getResourceIdFromHandle(long resourceHandle) {
if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
- return resourceHandle;
+ return (int) resourceHandle;
}
- return (resourceHandle & 0x00ff0000) >> 16;
+ return (int) ((resourceHandle >> RESOURCE_ID_SHIFT) & RESOURCE_ID_MASK);
}
- private boolean validateResourceHandle(int resourceType, int resourceHandle) {
+ private boolean validateResourceHandle(int resourceType, long resourceHandle) {
if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE
- || ((resourceHandle & 0xff000000) >> 24) != resourceType) {
+ || ((resourceHandle >> RESOURCE_TYPE_SHIFT) & RESOURCE_TYPE_MASK) != resourceType) {
return false;
}
return true;
diff --git a/services/core/java/com/android/server/uri/NeededUriGrants.java b/services/core/java/com/android/server/uri/NeededUriGrants.java
index 8c8f55304fbb..2fe61e00c97e 100644
--- a/services/core/java/com/android/server/uri/NeededUriGrants.java
+++ b/services/core/java/com/android/server/uri/NeededUriGrants.java
@@ -17,10 +17,13 @@
package com.android.server.uri;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.server.am.NeededUriGrantsProto;
+import java.util.Objects;
+
/** List of {@link GrantUri} a process needs. */
public class NeededUriGrants {
final String targetPkg;
@@ -35,6 +38,20 @@ public class NeededUriGrants {
this.uris = new ArraySet<>();
}
+ public void merge(NeededUriGrants other) {
+ if (other == null) return;
+ if (!Objects.equals(this.targetPkg, other.targetPkg)
+ || this.targetUid != other.targetUid || this.flags != other.flags) {
+ Slog.wtf("NeededUriGrants",
+ "The other NeededUriGrants does not share the same targetUid, targetPkg or "
+ + "flags. It cannot be merged into this NeededUriGrants. This "
+ + "NeededUriGrants: " + this.toStringWithoutUri()
+ + ". Other NeededUriGrants: " + other.toStringWithoutUri());
+ } else {
+ this.uris.addAll(other.uris);
+ }
+ }
+
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg);
@@ -47,4 +64,12 @@ public class NeededUriGrants {
}
proto.end(token);
}
+
+ public String toStringWithoutUri() {
+ return "NeededUriGrants{" +
+ "targetPkg='" + targetPkg + '\'' +
+ ", targetUid=" + targetUid +
+ ", flags=" + flags +
+ '}';
+ }
}
diff --git a/services/core/java/com/android/server/uri/UriPermission.java b/services/core/java/com/android/server/uri/UriPermission.java
index 0d1f36794f49..0ff23eab472a 100644
--- a/services/core/java/com/android/server/uri/UriPermission.java
+++ b/services/core/java/com/android/server/uri/UriPermission.java
@@ -25,6 +25,8 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
import com.google.android.collect.Sets;
import java.io.PrintWriter;
@@ -82,7 +84,9 @@ final class UriPermission {
static final long INVALID_TIME = Long.MIN_VALUE;
+ @GuardedBy("this")
private ArraySet<UriPermissionOwner> mReadOwners;
+ @GuardedBy("this")
private ArraySet<UriPermissionOwner> mWriteOwners;
private String stringName;
@@ -204,14 +208,16 @@ final class UriPermission {
persistedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
}
globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
- if (mReadOwners != null && includingOwners) {
- ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
- for (UriPermissionOwner r : mReadOwners) {
- if (r != null) {
- r.removeReadPermission(this);
+ synchronized (this) {
+ if (mReadOwners != null && includingOwners) {
+ ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ for (UriPermissionOwner r : mReadOwners) {
+ if (r != null) {
+ r.removeReadPermission(this);
+ }
}
+ mReadOwners = null;
}
- mReadOwners = null;
}
}
if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
@@ -220,14 +226,16 @@ final class UriPermission {
persistedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
}
globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
- if (mWriteOwners != null && includingOwners) {
- ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
- for (UriPermissionOwner r : mWriteOwners) {
- if (r != null) {
- r.removeWritePermission(this);
+ synchronized (this) {
+ if (mWriteOwners != null && includingOwners) {
+ ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+ for (UriPermissionOwner r : mWriteOwners) {
+ if (r != null) {
+ r.removeWritePermission(this);
+ }
}
+ mWriteOwners = null;
}
- mWriteOwners = null;
}
}
@@ -256,7 +264,7 @@ final class UriPermission {
}
}
- private void addReadOwner(UriPermissionOwner owner) {
+ private synchronized void addReadOwner(UriPermissionOwner owner) {
if (mReadOwners == null) {
mReadOwners = Sets.newArraySet();
ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
@@ -270,7 +278,7 @@ final class UriPermission {
/**
* Remove given read owner, updating {@Link #modeFlags} as needed.
*/
- void removeReadOwner(UriPermissionOwner owner) {
+ synchronized void removeReadOwner(UriPermissionOwner owner) {
if (mReadOwners == null || !mReadOwners.remove(owner)) {
Slog.wtf(TAG, "Unknown read owner " + owner + " in " + this);
return;
@@ -282,7 +290,7 @@ final class UriPermission {
}
}
- private void addWriteOwner(UriPermissionOwner owner) {
+ private synchronized void addWriteOwner(UriPermissionOwner owner) {
if (mWriteOwners == null) {
mWriteOwners = Sets.newArraySet();
ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
@@ -296,7 +304,7 @@ final class UriPermission {
/**
* Remove given write owner, updating {@Link #modeFlags} as needed.
*/
- void removeWriteOwner(UriPermissionOwner owner) {
+ synchronized void removeWriteOwner(UriPermissionOwner owner) {
if (mWriteOwners == null || !mWriteOwners.remove(owner)) {
Slog.wtf(TAG, "Unknown write owner " + owner + " in " + this);
return;
@@ -339,20 +347,22 @@ final class UriPermission {
}
pw.println();
- if (mReadOwners != null) {
- pw.print(prefix);
- pw.println("readOwners:");
- for (UriPermissionOwner owner : mReadOwners) {
+ synchronized (this) {
+ if (mReadOwners != null) {
pw.print(prefix);
- pw.println(" * " + owner);
+ pw.println("readOwners:");
+ for (UriPermissionOwner owner : mReadOwners) {
+ pw.print(prefix);
+ pw.println(" * " + owner);
+ }
}
- }
- if (mWriteOwners != null) {
- pw.print(prefix);
- pw.println("writeOwners:");
- for (UriPermissionOwner owner : mWriteOwners) {
+ if (mWriteOwners != null) {
pw.print(prefix);
- pw.println(" * " + owner);
+ pw.println("writeOwners:");
+ for (UriPermissionOwner owner : mWriteOwners) {
+ pw.print(prefix);
+ pw.println(" * " + owner);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 42203b113498..07d9ad16aca5 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -112,6 +112,14 @@ abstract class AbstractVibratorStep extends Step {
}
protected void stopVibrating() {
+ if (conductor.isInSession) {
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Vibration in session, skipping request to turn off vibrator "
+ + getVibratorId());
+ }
+ return;
+ }
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG,
"Turning off vibrator " + getVibratorId());
diff --git a/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java b/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
new file mode 100644
index 000000000000..54ae047a2858
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.BasicPwleSegment;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PwleSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Adapts {@link BasicPwleSegment} instances to device-specific {@link PwleSegment}
+ * representations, considering device capabilities such as the supported frequency range
+ * (defined by the intersection points of the frequency-acceleration response curve with the
+ * minimum sensitivity threshold) and the maximum achievable sensitivity level.
+ *
+ * <p>The segments will not be changed if the device doesn't have
+ * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}.
+ */
+final class BasicToPwleSegmentAdapter implements VibrationSegmentsAdapter {
+ private static final String TAG = "BasicToPwleSegmentAdapter";
+ private static final int MIN_REQUIRED_SENSITIVITY_DB_SL = 10;
+ /**
+ * An array of (frequency in Hz, minimum perceptible acceleration in dB) pairs.
+ * Each pair represents the minimum output level (in dB) required for a human to perceive the
+ * vibration at the corresponding frequency.
+ */
+ private static final Pair<Float, Float>[] MIN_PERCEPTIBLE_CURVE = new Pair[]{
+ Pair.create(0.4f, -97.81f), Pair.create(2.0f, -69.86f),
+ Pair.create(3.0f, -62.81f), Pair.create(4.0f, -58.81f),
+ Pair.create(5.0f, -56.69f), Pair.create(6.0f, -54.77f),
+ Pair.create(7.2f, -52.85f), Pair.create(8.0f, -51.77f),
+ Pair.create(8.64f, -50.84f), Pair.create(10.0f, -48.90f),
+ Pair.create(10.37f, -48.52f), Pair.create(12.44f, -46.50f),
+ Pair.create(14.93f, -44.43f), Pair.create(15.0f, -44.35f),
+ Pair.create(17.92f, -41.96f), Pair.create(20.0f, -40.36f),
+ Pair.create(21.5f, -39.60f), Pair.create(25.0f, -37.48f),
+ Pair.create(25.8f, -36.93f), Pair.create(30.0f, -34.31f),
+ Pair.create(35.0f, -33.13f), Pair.create(40.0f, -32.81f),
+ Pair.create(50.0f, -31.94f), Pair.create(60.0f, -31.77f),
+ Pair.create(70.0f, -31.59f), Pair.create(72.0f, -31.55f),
+ Pair.create(80.0f, -31.77f), Pair.create(86.4f, -31.94f),
+ Pair.create(90.0f, -31.73f), Pair.create(100.0f, -31.90f),
+ Pair.create(103.68f, -31.77f), Pair.create(124.42f, -31.70f),
+ Pair.create(149.3f, -31.38f), Pair.create(150.0f, -31.35f),
+ Pair.create(179.16f, -31.02f), Pair.create(200.0f, -30.86f),
+ Pair.create(215.0f, -30.35f), Pair.create(250.0f, -28.98f),
+ Pair.create(258.0f, -28.68f), Pair.create(300.0f, -26.81f),
+ Pair.create(400.0f, -19.81f)
+ };
+ private static final float[] sMinPerceptibleFrequenciesHz =
+ new float[MIN_PERCEPTIBLE_CURVE.length];
+ private static final float[] sMinPerceptibleAccelerationsDb =
+ new float[MIN_PERCEPTIBLE_CURVE.length];
+
+ BasicToPwleSegmentAdapter() {
+
+ // Sort the 'MIN_PERCEPTIBLE_LEVEL' data in ascending order based on the
+ // frequency values (first element of each pair).
+ Arrays.sort(MIN_PERCEPTIBLE_CURVE, Comparator.comparing(pair -> pair.first));
+
+ for (int i = 0; i < MIN_PERCEPTIBLE_CURVE.length; i++) {
+ sMinPerceptibleFrequenciesHz[i] = MIN_PERCEPTIBLE_CURVE[i].first;
+ sMinPerceptibleAccelerationsDb[i] = MIN_PERCEPTIBLE_CURVE[i].second;
+ }
+ }
+
+ @Override
+ public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+ int repeatIndex) {
+ if (!Flags.normalizedPwleEffects()
+ || !info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
+ // The vibrator does not have PWLE v2 capability, so keep the segments unchanged.
+ return repeatIndex;
+ }
+
+ VibratorInfo.FrequencyProfile frequencyProfile = info.getFrequencyProfile();
+ float[] frequenciesHz = frequencyProfile.getFrequenciesHz();
+ float[] accelerationsGs = frequencyProfile.getOutputAccelerationsGs();
+
+ Pair<Float, Float> frequencyRangeHz = calculateFrequencyRangeHz(
+ Objects.requireNonNull(frequenciesHz), Objects.requireNonNull(accelerationsGs));
+
+ if (frequencyRangeHz == null) {
+ // Failed to retrieve frequency range, so keep the segments unchanged.
+ return repeatIndex;
+ }
+ float minFrequencyHz = frequencyRangeHz.first;
+ float maxFrequencyHz = frequencyRangeHz.second;
+ float maxSensitivityLevel = getMaxSensitivityLevel(frequenciesHz, accelerationsGs,
+ minFrequencyHz, maxFrequencyHz);
+
+ for (int i = 0; i < segments.size(); i++) {
+ VibrationEffectSegment segment = segments.get(i);
+ if (segment instanceof BasicPwleSegment basicPwleSegment) {
+ PwleSegment pwleSegment = convertBasicToPwleSegment(frequencyProfile,
+ basicPwleSegment, minFrequencyHz, maxFrequencyHz,
+ maxSensitivityLevel);
+ segments.set(i, pwleSegment);
+ }
+ }
+
+ return repeatIndex;
+ }
+
+ /**
+ * Returns the supported frequency range within which {@link BasicPwleSegment}s are created.
+ * This range, also referred to as the "sharpness range", is defined by the
+ * minimum and maximum frequencies where the actuator's output acceleration exceeds the minimum
+ * required sensitivity level.
+ *
+ * <p>The minimum frequency is the first point where the actuator's frequency to output
+ * acceleration response curve intersects the minimum sensitivity threshold. The maximum
+ * frequency is determined by the second intersection point, or the maximum available
+ * frequency if no second intersection exists.
+ *
+ * @return The supported frequency range, or null if the minimum frequency cannot be determined.
+ */
+ @Nullable
+ private static Pair<Float, Float> calculateFrequencyRangeHz(@NonNull float[] frequenciesHz,
+ @NonNull float[] accelerationsGs) {
+ float minFrequencyHz = Float.NaN;
+ float maxFrequencyHz = Float.NaN;
+
+ for (int i = 0; i < frequenciesHz.length; i++) {
+ float minAcceptableOutputAcceleration = convertSensitivityLevelToAccelerationGs(
+ MIN_REQUIRED_SENSITIVITY_DB_SL, frequenciesHz[i]);
+
+ if (Float.isNaN(minFrequencyHz)
+ && minAcceptableOutputAcceleration <= accelerationsGs[i]) {
+ if (i == 0) {
+ minFrequencyHz = frequenciesHz[0];
+ } else {
+ minFrequencyHz = MathUtils.constrainedMap(
+ frequenciesHz[i - 1], frequenciesHz[i],
+ accelerationsGs[i - 1], accelerationsGs[i],
+ minAcceptableOutputAcceleration);
+ } // Found the lower bound
+ } else if (!Float.isNaN(minFrequencyHz)
+ && minAcceptableOutputAcceleration >= accelerationsGs[i]) {
+ maxFrequencyHz = MathUtils.constrainedMap(
+ frequenciesHz[i - 1], frequenciesHz[i],
+ accelerationsGs[i - 1], accelerationsGs[i],
+ minAcceptableOutputAcceleration); // Found the upper bound
+ return new Pair<>(minFrequencyHz, maxFrequencyHz);
+ }
+ }
+
+ if (Float.isNaN(minFrequencyHz)) {
+ // Lower bound was not found
+ Slog.e(TAG,
+ "Failed to retrieve frequency range. A valid frequency range must be "
+ + "available to create envelope vibration effects.");
+ return null;
+ }
+
+ // If only the lower bound was found, set the upper bound to the maximum frequency.
+ maxFrequencyHz = frequenciesHz[frequenciesHz.length - 1];
+
+ return new Pair<>(minFrequencyHz, maxFrequencyHz);
+ }
+
+ /**
+ * Converts the {@link BasicPwleSegment} to its equivalent {@link PwleSegment} based on the
+ * devices capabilities.
+ */
+ private static PwleSegment convertBasicToPwleSegment(
+ @NonNull VibratorInfo.FrequencyProfile frequencyProfile,
+ @NonNull BasicPwleSegment basicPwleSegment, float minFrequencyHz, float maxFrequencyHz,
+ float maxSensitivityLevel) {
+
+ float startFrequency = convertSharpnessToFrequencyHz(basicPwleSegment.getStartSharpness(),
+ minFrequencyHz, maxFrequencyHz);
+ float endFrequency = convertSharpnessToFrequencyHz(basicPwleSegment.getEndSharpness(),
+ minFrequencyHz, maxFrequencyHz);
+
+ float startAmplitude = convertIntensityToAmplitude(frequencyProfile,
+ basicPwleSegment.getStartIntensity(), startFrequency, maxSensitivityLevel);
+ float endAmplitude = convertIntensityToAmplitude(frequencyProfile,
+ basicPwleSegment.getEndIntensity(), endFrequency, maxSensitivityLevel);
+
+ return new PwleSegment(startAmplitude, endAmplitude, startFrequency, endFrequency,
+ basicPwleSegment.getDuration());
+ }
+
+ /**
+ * Calculates the amplitude for the vibrator, ranging [0.0, 1.0], based on the desired
+ * intensity and the vibrator's capabilities at the specified frequency.
+ *
+ * <p>This method first converts the desired intensity to an equivalent acceleration value
+ * based on the maximum sensitivity level within the sharpness range. It then compares this
+ * desired acceleration to the maximum acceleration the vibrator can produce at the given
+ * frequency.
+ *
+ * <p>If the desired acceleration exceeds the vibrator's capability, the method returns
+ * 1.0 (maximum amplitude). Otherwise, it returns a normalized amplitude value, calculated as
+ * the ratio of the desired acceleration to the maximum available acceleration at the given
+ * frequency.
+ */
+ private static float convertIntensityToAmplitude(VibratorInfo.FrequencyProfile frequencyProfile,
+ float intensity, float frequencyHz, float maxSensitivityLevel) {
+ if (intensity == 0) {
+ // Zero intensity should map to zero amplitude (i.e. vibrator off)
+ // instead of 0 db SL (i.e. the minimum perceivable output).
+ // This is for consistency with waveform envelopes, to ensure effects
+ // are able to ramp from/to the vibrator off state.
+ return 0;
+ }
+
+ float desiredAcceleration = convertIntensityToAccelerationGs(intensity, frequencyHz,
+ maxSensitivityLevel);
+ float availableAcceleration = frequencyProfile.getOutputAccelerationGs(
+ frequencyHz);
+ return desiredAcceleration >= availableAcceleration ? 1.0f
+ : desiredAcceleration / availableAcceleration;
+ }
+
+ private static float getMaxSensitivityLevel(float[] frequenciesHz, float[] accelerationsGs,
+ float minFrequencyHz, float maxFrequencyHz) {
+ float maxAccelerationGs = Float.MIN_VALUE;
+ int maxAccelerationIndex = -1;
+ for (int i = 0; i < frequenciesHz.length; i++) {
+ float frequency = frequenciesHz[i];
+ if (frequency < minFrequencyHz) {
+ continue;
+ }
+ if (frequency > maxFrequencyHz) {
+ break;
+ }
+ if (accelerationsGs[i] > maxAccelerationGs) {
+ maxAccelerationGs = accelerationsGs[i];
+ maxAccelerationIndex = i;
+ }
+ }
+
+ return convertDecibelToSensitivityLevel(convertAccelerationToDecibel(maxAccelerationGs),
+ frequenciesHz[maxAccelerationIndex]);
+ }
+
+ private static float convertSharpnessToFrequencyHz(float sharpness, float minFrequencyHz,
+ float maxFrequencyHz) {
+ return minFrequencyHz + sharpness * (maxFrequencyHz - minFrequencyHz);
+ }
+
+ private static float convertIntensityToAccelerationGs(float intensity, float frequencyHz,
+ float maxSensitivityLevel) {
+ return convertSensitivityLevelToAccelerationGs(intensity * maxSensitivityLevel,
+ frequencyHz);
+ }
+
+ private static float convertSensitivityLevelToAccelerationGs(float sensitivityLevel,
+ float frequencyHz) {
+ return convertDecibelToAccelerationGs(
+ convertSensitivityLevelToDecibel(sensitivityLevel, frequencyHz));
+ }
+
+ private static float convertDecibelToAccelerationGs(float db) {
+ return (float) Math.pow(10, db / 20);
+ }
+
+ private static float convertSensitivityLevelToDecibel(float sensitivityLevel,
+ float frequencyHz) {
+ float minPerceptibleDbAtFrequency = getMinPerceptibleAccelerationDb(frequencyHz);
+ return sensitivityLevel + minPerceptibleDbAtFrequency;
+ }
+
+ private static float convertAccelerationToDecibel(float accelerationGs) {
+ return (float) (20 * Math.log10(accelerationGs));
+ }
+
+ private static float convertDecibelToSensitivityLevel(float db, float frequencyHz) {
+ float minPerceptibleDbAtFrequency = getMinPerceptibleAccelerationDb(frequencyHz);
+ return db - minPerceptibleDbAtFrequency;
+ }
+
+ /**
+ * Retrieves the minimum perceptible acceleration, in dB, for the specified frequency (hz).
+ */
+ private static float getMinPerceptibleAccelerationDb(float frequencyHz) {
+
+ if (frequencyHz <= sMinPerceptibleFrequenciesHz[0]) {
+ return sMinPerceptibleAccelerationsDb[0];
+ }
+ if (frequencyHz >= sMinPerceptibleFrequenciesHz[sMinPerceptibleFrequenciesHz.length - 1]) {
+ return sMinPerceptibleAccelerationsDb[sMinPerceptibleAccelerationsDb.length - 1];
+ }
+
+ int idx = Arrays.binarySearch(sMinPerceptibleFrequenciesHz, frequencyHz);
+ if (idx >= 0) {
+ return sMinPerceptibleAccelerationsDb[idx];
+ }
+ // This indicates that the value was not found in the list. Adjust index of the
+ // insertion point to be at the lower bound.
+ idx = -idx - 2;
+
+ return MathUtils.constrainedMap(
+ sMinPerceptibleAccelerationsDb[idx],
+ sMinPerceptibleAccelerationsDb[idx + 1],
+ sMinPerceptibleFrequenciesHz[idx], sMinPerceptibleFrequenciesHz[idx + 1],
+ frequencyHz);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 111e075a7374..245d9020d5a2 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -26,10 +26,10 @@ import java.util.List;
/**
* Adapter that clips frequency values to the supported range specified by
- * {@link VibratorInfo.FrequencyProfile}, then clips amplitude values to the max supported one at
+ * {@link VibratorInfo.FrequencyProfileLegacy}, then clips amplitude values to the max supported one at
* each frequency.
*
- * <p>The {@link VibratorInfo.FrequencyProfile} is only applicable to PWLE compositions. This
+ * <p>The {@link VibratorInfo.FrequencyProfileLegacy} is only applicable to PWLE compositions. This
* adapter is only applied to {@link RampSegment} and all other segments will remain unchanged.
*/
final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAdapter {
@@ -59,7 +59,7 @@ final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAda
}
private float clampFrequency(VibratorInfo info, float frequencyHz) {
- Range<Float> frequencyRangeHz = info.getFrequencyProfile().getFrequencyRangeHz();
+ Range<Float> frequencyRangeHz = info.getFrequencyProfileLegacy().getFrequencyRangeHz();
if (frequencyHz == 0 || frequencyRangeHz == null) {
return Float.isNaN(info.getResonantFrequencyHz()) ? 0 : info.getResonantFrequencyHz();
}
@@ -67,7 +67,7 @@ final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAda
}
private float clampAmplitude(VibratorInfo info, float frequencyHz, float amplitude) {
- VibratorInfo.FrequencyProfile mapping = info.getFrequencyProfile();
+ VibratorInfo.FrequencyProfileLegacy mapping = info.getFrequencyProfileLegacy();
if (mapping.isEmpty()) {
// No frequency mapping was specified so leave amplitude unchanged.
// The frequency will be clamped to the device's resonant frequency.
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
new file mode 100644
index 000000000000..32a322725692
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PwlePoint;
+import android.os.vibrator.PwleSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of PWLE segments.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link PwleSegment}, starting at the current index.
+ */
+final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
+
+ ComposePwleV2VibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long pendingVibratorOffDeadline) {
+ // This step should wait for the last vibration to finish (with the timeout) and for the
+ // intended step start time (to respect the effect delays).
+ super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+ index, pendingVibratorOffDeadline);
+ }
+
+ @NonNull
+ @Override
+ public List<Step> play() {
+ if (!Flags.normalizedPwleEffects()) {
+ // Skip this step and play the next one right away.
+ return nextSteps(/* segmentsPlayed= */ 1);
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleV2Step");
+ try {
+ // Load the next PwleSegments to create a single composePwleV2 call to the vibrator,
+ // limited to the vibrator's maximum envelope effect size.
+ int limit = controller.getVibratorInfo().getMaxEnvelopeEffectSize();
+ List<PwlePoint> pwles = unrollPwleSegments(effect, segmentIndex, limit);
+
+ if (pwles.isEmpty()) {
+ Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: "
+ + effect.getSegments().get(segmentIndex));
+ // Skip this step and play the next one right away.
+ return nextSteps(/* segmentsPlayed= */ 1);
+ }
+
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+ + controller.getVibratorInfo().getId());
+ }
+ PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
+ long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ handleVibratorOnResult(vibratorOnResult);
+ getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
+
+ // The next start and off times will be calculated from mVibratorOnResult.
+ return nextSteps(/* segmentsPlayed= */ pwles.size());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private List<PwlePoint> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
+ int limit) {
+ List<PwlePoint> pwlePoints = new ArrayList<>(limit);
+ float bestBreakAmplitude = 1;
+ int bestBreakPosition = limit; // Exclusive index.
+
+ int segmentCount = effect.getSegments().size();
+ int repeatIndex = effect.getRepeatIndex();
+
+ // Loop once after reaching the limit to see if breaking it will really be necessary, then
+ // apply the best break position found, otherwise return the full list as it fits the limit.
+ for (int i = startIndex; pwlePoints.size() < limit; i++) {
+ if (i == segmentCount) {
+ if (repeatIndex >= 0) {
+ i = repeatIndex;
+ } else {
+ // Non-repeating effect, stop collecting pwles.
+ break;
+ }
+ }
+ VibrationEffectSegment segment = effect.getSegments().get(i);
+ if (segment instanceof PwleSegment pwleSegment) {
+ if (pwlePoints.isEmpty()) {
+ // The initial state is defined by the starting amplitude and frequency of the
+ // first PwleSegment. The time parameter is set to zero to indicate this is
+ // the initial condition without any ramp up time.
+ pwlePoints.add(new PwlePoint(pwleSegment.getStartAmplitude(),
+ pwleSegment.getStartFrequencyHz(), /*timeMillis=*/ 0));
+ }
+ pwlePoints.add(new PwlePoint(pwleSegment.getEndAmplitude(),
+ pwleSegment.getEndFrequencyHz(), (int) pwleSegment.getDuration()));
+
+ if (isBetterBreakPosition(pwlePoints, bestBreakAmplitude, limit)) {
+ // Mark this position as the best one so far to break a long waveform.
+ bestBreakAmplitude = pwleSegment.getEndAmplitude();
+ bestBreakPosition = pwlePoints.size(); // Break after this pwle ends.
+ }
+ } else {
+ // First non-pwle segment, stop collecting pwles.
+ break;
+ }
+ }
+
+ return pwlePoints.size() > limit
+ // Remove excessive segments, using the best breaking position recorded.
+ ? pwlePoints.subList(0, bestBreakPosition)
+ // Return all collected pwle segments.
+ : pwlePoints;
+ }
+
+ /**
+ * Returns true if the current segment list represents a better break position for a PWLE,
+ * given the current amplitude being used for breaking it at a smaller size and the size limit.
+ */
+ private boolean isBetterBreakPosition(List<PwlePoint> segments,
+ float currentBestBreakAmplitude, int limit) {
+ PwlePoint lastSegment = segments.get(segments.size() - 1);
+ float breakAmplitudeCandidate = lastSegment.getAmplitude();
+ int breakPositionCandidate = segments.size();
+
+ if (breakPositionCandidate > limit) {
+ // We're beyond limit, last break position found should be used.
+ return false;
+ }
+ if (breakAmplitudeCandidate == 0) {
+ // Breaking at amplitude zero at any position is always preferable.
+ return true;
+ }
+ if (breakPositionCandidate < limit / 2) {
+ // Avoid breaking at the first half of the allowed maximum size, even if amplitudes are
+ // lower, to avoid creating PWLEs that are too small unless it's to break at zero.
+ return false;
+ }
+ // Prefer lower amplitudes at a later position for breaking the PWLE in a more subtle way.
+ return breakAmplitudeCandidate <= currentBestBreakAmplitude;
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
index bd4fc07fe816..e4542b359476 100644
--- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -47,11 +47,17 @@ final class DeviceAdapter implements CombinedVibration.VibratorAdapter {
* instance is created with the final segment list.
*/
private final List<VibrationSegmentsAdapter> mSegmentAdapters;
+ /**
+ * The vibration segment validators that can validate VibrationEffectSegments entries based on
+ * the VibratorInfo.
+ */
+ private final List<VibrationSegmentsValidator> mSegmentsValidators;
DeviceAdapter(VibrationSettings settings, SparseArray<VibratorController> vibrators) {
mSegmentAdapters = Arrays.asList(
- // TODO(b/167947076): add filter that removes unsupported primitives
// TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+ // Updates primitive delays to hardware supported pauses
+ new PrimitiveDelayAdapter(),
// Convert segments based on device capabilities
new RampToStepAdapter(settings.getRampStepDuration()),
new StepToRampAdapter(),
@@ -60,7 +66,17 @@ final class DeviceAdapter implements CombinedVibration.VibratorAdapter {
// Split segments based on their duration and device supported limits
new SplitSegmentsAdapter(),
// Clip amplitudes and frequencies of final segments based on device bandwidth curve
- new ClippingAmplitudeAndFrequencyAdapter()
+ new ClippingAmplitudeAndFrequencyAdapter(),
+ // Convert BasicPwleSegments to PwleSegments based on device capabilities
+ new BasicToPwleSegmentAdapter(),
+ // Split Pwle segments based on their duration and device supported limits
+ new SplitPwleSegmentsAdapter()
+ );
+ mSegmentsValidators = List.of(
+ // Validate Pwle segments base on the vibrators frequency range
+ new PwleSegmentsValidator(),
+ // Validate primitive segments based on device support
+ new PrimitiveSegmentsValidator()
);
mAvailableVibrators = vibrators;
mAvailableVibratorIds = new int[vibrators.size()];
@@ -78,7 +94,6 @@ final class DeviceAdapter implements CombinedVibration.VibratorAdapter {
return mAvailableVibratorIds;
}
- @NonNull
@Override
public VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect) {
if (!(effect instanceof VibrationEffect.Composed composed)) {
@@ -102,6 +117,14 @@ final class DeviceAdapter implements CombinedVibration.VibratorAdapter {
mSegmentAdapters.get(i).adaptToVibrator(info, newSegments, newRepeatIndex);
}
+ // Validate the vibration segments. If a segment is not supported, ignore the entire
+ // vibration effect.
+ for (int i = 0; i < mSegmentsValidators.size(); i++) {
+ if (!mSegmentsValidators.get(i).hasValidSegments(info, newSegments)) {
+ return null;
+ }
+ }
+
return new VibrationEffect.Composed(newSegments, newRepeatIndex);
}
}
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index b4d3862431f1..a92ac679b0f4 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.ExternalVibration;
@@ -23,6 +24,7 @@ import android.os.ExternalVibrationScale;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.vibrator.Flags;
+import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -31,20 +33,32 @@ import com.android.internal.util.FrameworkStatsLog;
*/
final class ExternalVibrationSession extends Vibration
implements VibrationSession, IBinder.DeathRecipient {
+ private static final String TAG = "ExternalVibrationSession";
+ /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
+ interface VibratorManagerHooks {
+
+ /**
+ * Tells the manager that the external vibration is finished and the vibrators can now be
+ * used for another vibration.
+ */
+ void onExternalVibrationReleased(long vibrationId);
+ }
+
+ private final long mSessionId = VibrationSession.nextSessionId();
private final ExternalVibration mExternalVibration;
private final ExternalVibrationScale mScale = new ExternalVibrationScale();
+ private final VibratorManagerHooks mManagerHooks;
- @Nullable
- private Runnable mBinderDeathCallback;
-
- ExternalVibrationSession(ExternalVibration externalVibration) {
- super(externalVibration.getToken(), new CallerInfo(
+ ExternalVibrationSession(ExternalVibration externalVibration,
+ VibratorManagerHooks managerHooks) {
+ super(new CallerInfo(
externalVibration.getVibrationAttributes(), externalVibration.getUid(),
// TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
// instead of using DEVICE_ID_INVALID here and relying on the UID checks.
Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
mExternalVibration = externalVibration;
+ mManagerHooks = managerHooks;
}
public ExternalVibrationScale getScale() {
@@ -52,24 +66,31 @@ final class ExternalVibrationSession extends Vibration
}
@Override
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return stats.getCreateUptimeMillis();
+ }
+
+ @Override
public CallerInfo getCallerInfo() {
return callerInfo;
}
@Override
- public VibrationSession.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
- /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
- callerInfo);
+ public IBinder getCallerToken() {
+ return mExternalVibration.getToken();
}
@Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- return new VibrationStats.StatsInfo(
- mExternalVibration.getUid(),
- FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
- mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
- completionUptimeMillis);
+ public VibrationSession.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats,
+ /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel,
+ mScale.adaptiveHapticsScale);
}
@Override
@@ -82,46 +103,63 @@ final class ExternalVibrationSession extends Vibration
}
@Override
- public void linkToDeath(Runnable callback) {
- synchronized (this) {
- mBinderDeathCallback = callback;
- }
+ public boolean wasEndRequested() {
+ // End request is immediate, so just check if vibration has already ended.
+ return hasEnded();
+ }
+
+ @Override
+ public boolean linkToDeath() {
mExternalVibration.linkToDeath(this);
+ return true;
}
@Override
public void unlinkToDeath() {
mExternalVibration.unlinkToDeath(this);
- synchronized (this) {
- mBinderDeathCallback = null;
- }
}
@Override
public void binderDied() {
- synchronized (this) {
- if (mBinderDeathCallback != null) {
- mBinderDeathCallback.run();
- }
- }
+ Slog.d(TAG, "Binder died, cancelling external vibration...");
+ requestEnd(Status.CANCELLED_BINDER_DIED);
}
@Override
void end(EndInfo endInfo) {
super.end(endInfo);
if (stats.hasStarted()) {
+ // Notify external client that this vibration should stop sending data to the vibrator.
+ mExternalVibration.mute();
// External vibration doesn't have feedback from total time the vibrator was playing
// with non-zero amplitude, so we use the duration between start and end times of
// the vibration as the time the vibrator was ON, since the haptic channels are
// open for this duration and can receive vibration waveform data.
stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+ // Notify the manager that external client has released the vibrator control.
+ mManagerHooks.onExternalVibrationReleased(id);
}
}
@Override
- public void notifyEnded() {
- // Notify external client that this vibration should stop sending data to the vibrator.
- mExternalVibration.mute();
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ end(new EndInfo(status, endedBy));
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator manager for sync
+ }
+
+ @Override
+ public void notifySessionCallback() {
+ // ignored, external control does not expect callbacks from the vibrator manager for session
}
boolean isHoldingSameVibration(ExternalVibration vib) {
@@ -143,4 +181,15 @@ final class ExternalVibrationSession extends Vibration
mScale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
stats.reportAdaptiveScale(mScale.adaptiveHapticsScale);
}
+
+ @Override
+ public String toString() {
+ return "ExternalVibrationSession{"
+ + "sessionId=" + mSessionId
+ + ", vibrationId=" + id
+ + ", callerInfo=" + callerInfo
+ + ", externalVibration=" + mExternalVibration
+ + ", scale=" + mScale
+ + '}';
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index ea4bd0182ea3..c9f1e4b38dce 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -19,15 +19,18 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
-import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
-import com.android.internal.util.FrameworkStatsLog;
-
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.function.IntFunction;
/**
* Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -55,9 +58,9 @@ final class HalVibration extends Vibration {
private int mScaleLevel;
private float mAdaptiveScale;
- HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
- @NonNull VibrationSession.CallerInfo callerInfo) {
- super(token, callerInfo);
+ HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
+ @NonNull CombinedVibration effect) {
+ super(callerInfo);
mOriginalEffect = effect;
mEffectToPlay = effect;
mScaleLevel = VibrationScaler.SCALE_NONE;
@@ -85,11 +88,11 @@ final class HalVibration extends Vibration {
}
/**
- * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
- * which might be necessary for replacement in realtime.
+ * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
+ * might be necessary for replacement in realtime.
*/
- public void addFallback(int effectId, VibrationEffect effect) {
- mFallbacks.put(effectId, effect);
+ public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
+ fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
}
/**
@@ -121,17 +124,18 @@ final class HalVibration extends Vibration {
* @param deviceAdapter A {@link CombinedVibration.VibratorAdapter} that transforms vibration
* effects to device vibrators based on its capabilities.
*/
- public void adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) {
- CombinedVibration newEffect = mEffectToPlay.adapt(deviceAdapter);
- if (!Objects.equals(mEffectToPlay, newEffect)) {
- mEffectToPlay = newEffect;
+ public boolean adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) {
+ CombinedVibration adaptedEffect = mEffectToPlay.adapt(deviceAdapter);
+ if (adaptedEffect == null) {
+ return false;
+ }
+
+ if (!mEffectToPlay.equals(adaptedEffect)) {
+ mEffectToPlay = adaptedEffect;
}
// No need to update fallback effects, they are already configured per device.
- }
- @Override
- public boolean isRepeating() {
- return mOriginalEffect.getDuration() == Long.MAX_VALUE;
+ return true;
}
/** Return the effect that should be played by this vibration. */
@@ -144,34 +148,70 @@ final class HalVibration extends Vibration {
// Clear the original effect if it's the same as the effect that was played, for simplicity
CombinedVibration originalEffect =
Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
- return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
- mScaleLevel, mAdaptiveScale, callerInfo);
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
+ originalEffect, mScaleLevel, mAdaptiveScale);
}
- @Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- int vibrationType = mEffectToPlay.hasVendorEffects()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
- : isRepeating()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
- : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
- return new VibrationStats.StatsInfo(
- callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
- stats, completionUptimeMillis);
+ /** Returns true if this vibration can pipeline with the specified one. */
+ public boolean canPipelineWith(HalVibration vib,
+ @Nullable SparseArray<VibratorInfo> vibratorInfos, int durationThresholdMs) {
+ long effectDuration = Flags.vibrationPipelineEnabled() && (vibratorInfos != null)
+ ? mEffectToPlay.getDuration(vibratorInfos)
+ : mEffectToPlay.getDuration();
+ if (effectDuration == Long.MAX_VALUE) {
+ // Repeating vibrations can't pipeline with following vibrations, because the cancel()
+ // call to stop the repetition will cancel a pending vibration too. This can be changed
+ // if we have a use-case, requiring changes to how pipelined vibrations are cancelled.
+ return false;
+ }
+ if (Flags.vibrationPipelineEnabled()
+ && (effectDuration > 0) && (effectDuration < durationThresholdMs)) {
+ // Duration is known and it's less than the pipeline threshold, so allow it.
+ // No need to check UID, as we want to avoid cancelling any short effect and let the
+ // vibrator hardware gracefully finish the vibration.
+ return true;
+ }
+ // Check the same app is requesting multiple vibrations with the pipeline flag,
+ // independently of the effect durations.
+ return callerInfo.uid == vib.callerInfo.uid
+ && callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+ && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT);
}
- /**
- * Returns true if this vibration can pipeline with the specified one.
- *
- * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
- * because the cancel() call to stop the repetition will cancel a pending vibration too. This
- * can be changed if we have a use-case to reason around behavior for. It may also be nice to
- * pipeline very short vibrations together, regardless of the flag.
- */
- public boolean canPipelineWith(HalVibration vib) {
- return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && !isRepeating();
+ private void fillFallbacksForEffect(CombinedVibration effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (effect instanceof CombinedVibration.Mono) {
+ fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
+ } else if (effect instanceof CombinedVibration.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibration.Stereo) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
+ }
+ } else if (effect instanceof CombinedVibration.Sequential) {
+ List<CombinedVibration> effects =
+ ((CombinedVibration.Sequential) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.get(i), fallbackProvider);
+ }
+ }
+ }
+
+ private void fillFallbacksForEffect(VibrationEffect effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (!(effect instanceof VibrationEffect.Composed composed)) {
+ return;
+ }
+ int segmentCount = composed.getSegments().size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) {
+ VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
+ if (fallback != null) {
+ mFallbacks.put(prebaked.getEffectId(), fallback);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 006a5bb1d1d0..a235ba154579 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -148,8 +148,8 @@ public final class HapticFeedbackVibrationProvider {
case HapticFeedbackConstants.SCROLL_TICK:
case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
case HapticFeedbackConstants.SCROLL_LIMIT:
- attrs = hapticFeedbackInputSourceCustomizationEnabled() ? TOUCH_VIBRATION_ATTRIBUTES
- : HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+ // TODO(b/372820923): use touch attributes by default.
+ attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
break;
case HapticFeedbackConstants.KEYBOARD_TAP:
case HapticFeedbackConstants.KEYBOARD_RELEASE:
@@ -176,14 +176,15 @@ public final class HapticFeedbackVibrationProvider {
int inputSource,
@HapticFeedbackConstants.Flags int flags,
@HapticFeedbackConstants.PrivateFlags int privFlags) {
- if (hapticFeedbackInputSourceCustomizationEnabled()
- && inputSource == InputDevice.SOURCE_ROTARY_ENCODER) {
+ if (hapticFeedbackInputSourceCustomizationEnabled()) {
switch (effectId) {
case HapticFeedbackConstants.SCROLL_TICK,
HapticFeedbackConstants.SCROLL_ITEM_FOCUS,
HapticFeedbackConstants.SCROLL_LIMIT -> {
- return getVibrationAttributesWithFlags(HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES,
- effectId, flags);
+ VibrationAttributes attrs = inputSource == InputDevice.SOURCE_ROTARY_ENCODER
+ ? HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES
+ : TOUCH_VIBRATION_ATTRIBUTES;
+ return getVibrationAttributesWithFlags(attrs, effectId, flags);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java b/services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java
new file mode 100644
index 000000000000..d63fffd4fed3
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+
+import android.os.VibrationEffect.Composition.DelayType;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/**
+ * Adapter that converts between {@link DelayType} and the HAL supported pause delays.
+ *
+ * <p>Primitives that overlap due to the delays being shorter than the previous segments will be
+ * dropped from the effect here. Relative timings will still use the dropped primitives to preserve
+ * the design intention.
+ */
+final class PrimitiveDelayAdapter implements VibrationSegmentsAdapter {
+
+ PrimitiveDelayAdapter() {
+ }
+
+ @Override
+ public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+ int repeatIndex) {
+ if (!Flags.primitiveCompositionAbsoluteDelay()) {
+ return repeatIndex;
+ }
+ int previousStartOffset = 0;
+ int segmentCount = segments.size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = segments.get(i);
+ if (i == repeatIndex) {
+ // Crossed the repeat line, reset start offset so repeating block is independent.
+ previousStartOffset = 0;
+ }
+
+ if (!(segment instanceof PrimitiveSegment primitive)
+ || (primitive.getDelayType() == DELAY_TYPE_PAUSE)) {
+ // Effect will play normally, keep track of its start offset.
+ previousStartOffset = -calculateEffectDuration(info, segment);
+ continue;
+ }
+
+ int pause = calculatePause(primitive, previousStartOffset);
+ if (pause >= 0) {
+ segments.set(i, toPrimitiveWithPause(primitive, pause));
+ // Delay will be ignored from this calculation.
+ previousStartOffset = -calculateEffectDuration(info, primitive);
+ } else {
+ // Primitive overlapping with previous segment, ignore it.
+ segments.remove(i);
+ if (repeatIndex > i) {
+ repeatIndex--;
+ }
+ segmentCount--;
+ i--;
+
+ // Keep the intended start time for future calculations. Here is an example:
+ // 10 20 30 40 50 60 70 | Timeline (D = relative delay, E = effect duration)
+ // D E E E E | D=10, E=40 | offset = 0 | pause = 10 | OK
+ // D E E | D=10, E=20 | offset = -40 | pause = -30 | IGNORED
+ // D E E | D=10, E=20 | offset = -30 | pause = -20 | IGNORED
+ // D E E | D=10, E=20 | offset = -20 | pause = -10 | IGNORED
+ // D E E | D=10, E=20 | offset = -10 | pause = 0 | OK
+ previousStartOffset = pause;
+ }
+ }
+ return repeatIndex;
+ }
+
+ private static int calculatePause(PrimitiveSegment primitive, int previousStartOffset) {
+ if (primitive.getDelayType() == DELAY_TYPE_RELATIVE_START_OFFSET) {
+ return previousStartOffset + primitive.getDelay();
+ }
+ return primitive.getDelay();
+ }
+
+ private static int calculateEffectDuration(VibratorInfo info, VibrationEffectSegment segment) {
+ long segmentDuration = segment.getDuration(info);
+ if (segmentDuration < 0) {
+ // Duration unknown, default to zero.
+ return 0;
+ }
+ int effectDuration = (int) segmentDuration;
+ if (segment instanceof PrimitiveSegment primitive) {
+ // Ignore primitive delays from effect duration.
+ effectDuration -= primitive.getDelay();
+ }
+ return effectDuration;
+ }
+
+ private static PrimitiveSegment toPrimitiveWithPause(PrimitiveSegment primitive, int pause) {
+ return new PrimitiveSegment(primitive.getPrimitiveId(), primitive.getScale(),
+ pause, DELAY_TYPE_PAUSE);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java b/services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java
new file mode 100644
index 000000000000..a1567fc470b5
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.SuppressLint;
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/**
+ * Validates primitive segments to ensure they are compatible with the device's capabilities.
+ *
+ * <p>The segments will be considered invalid if the device does not have
+ * {@link IVibrator#CAP_COMPOSE_EFFECTS} or if one of the primitives is not supported.
+ */
+final class PrimitiveSegmentsValidator implements VibrationSegmentsValidator {
+
+ @SuppressLint("WrongConstant") // using primitive id from validated segment
+ @Override
+ public boolean hasValidSegments(VibratorInfo info, List<VibrationEffectSegment> segments) {
+ int segmentCount = segments.size();
+ for (int i = 0; i < segmentCount; i++) {
+ if (!(segments.get(i) instanceof PrimitiveSegment primitive)) {
+ continue;
+ }
+ if (Flags.primitiveCompositionAbsoluteDelay()) {
+ // Primitive support checks introduced by this feature
+ if (!info.isPrimitiveSupported(primitive.getPrimitiveId())) {
+ return false;
+ }
+ } else {
+ // Delay type support not available without this feature
+ if ((primitive.getDelayType() != PrimitiveSegment.DEFAULT_DELAY_TYPE)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/PwleSegmentsValidator.java b/services/core/java/com/android/server/vibrator/PwleSegmentsValidator.java
new file mode 100644
index 000000000000..85ba38d60dde
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PwleSegmentsValidator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.BasicPwleSegment;
+import android.os.vibrator.PwleSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/**
+ * Validates {@link PwleSegment} and {@link BasicPwleSegment} instances to ensure they are
+ * compatible with the device's capabilities.
+ *
+ * <p>This validator performs the following checks:
+ * <ul>
+ * <li>For {@link PwleSegment}:
+ * <ul>
+ * <li>Verifies that the device supports {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}.
+ * <li>Verifies that each segment's start and end frequencies fall within the supported range.
+ * </ul>
+ * </li>
+ * <li>For {@link BasicPwleSegment}:
+ * <ul>
+ * <li>Verifies that the device supports {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}.
+ * </ul>
+ * </li>
+ * </ul>
+ */
+final class PwleSegmentsValidator implements VibrationSegmentsValidator {
+
+ @Override
+ public boolean hasValidSegments(VibratorInfo info, List<VibrationEffectSegment> segments) {
+
+ boolean hasPwleCapability = info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ float minFrequency = info.getFrequencyProfile().getMinFrequencyHz();
+ float maxFrequency = info.getFrequencyProfile().getMaxFrequencyHz();
+
+ for (VibrationEffectSegment segment : segments) {
+ if (segment instanceof BasicPwleSegment && !hasPwleCapability) {
+ return false;
+ }
+ if (segment instanceof PwleSegment pwleSegment) {
+ if (!hasPwleCapability || pwleSegment.getStartFrequencyHz() < minFrequency
+ || pwleSegment.getStartFrequencyHz() > maxFrequency
+ || pwleSegment.getEndFrequencyHz() < minFrequency
+ || pwleSegment.getEndFrequencyHz() > maxFrequency) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
new file mode 100644
index 000000000000..628221b09d77
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session holding a single {@link CombinedVibration} request, performed by a
+ * {@link VibrationStepConductor}.
+ */
+final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient {
+ private static final String TAG = "SingleVibrationSession";
+
+ private final Object mLock = new Object();
+ private final long mSessionId = VibrationSession.nextSessionId();
+ private final IBinder mCallerToken;
+ private final HalVibration mVibration;
+
+ @GuardedBy("mLock")
+ private VibrationStepConductor mConductor;
+
+ SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo,
+ @NonNull CombinedVibration vibration) {
+ mCallerToken = callerToken;
+ mVibration = new HalVibration(callerInfo, vibration);
+ }
+
+ public void setVibrationConductor(@Nullable VibrationStepConductor conductor) {
+ synchronized (mLock) {
+ mConductor = conductor;
+ }
+ }
+
+ public HalVibration getVibration() {
+ return mVibration;
+ }
+
+ @Override
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mVibration.stats.getCreateUptimeMillis();
+ }
+
+ @Override
+ public boolean isRepeating() {
+ return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mVibration.callerInfo;
+ }
+
+ @Override
+ public IBinder getCallerToken() {
+ return mCallerToken;
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() {
+ return mVibration.getDebugInfo();
+ }
+
+ @Override
+ public boolean wasEndRequested() {
+ if (mVibration.hasEnded()) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mConductor != null && mConductor.wasNotifiedToCancel();
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.d(TAG, "Binder died, cancelling vibration...");
+ requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ }
+
+ @Override
+ public boolean linkToDeath() {
+ try {
+ mCallerToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error linking vibration to token death", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ try {
+ mCallerToken.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
+ }
+ }
+
+ @Override
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate);
+ } else {
+ mVibration.end(new Vibration.EndInfo(status, endedBy));
+ }
+ }
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyVibratorComplete(vibratorId);
+ }
+ }
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifySyncedVibrationComplete();
+ }
+ }
+ }
+
+ @Override
+ public void notifySessionCallback() {
+ // ignored, external control does not expect callbacks from the vibrator manager for session
+ }
+
+ @Override
+ public String toString() {
+ return "SingleVibrationSession{"
+ + "sessionId= " + mSessionId
+ + ", callerToken= " + mCallerToken
+ + ", vibration=" + mVibration
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java b/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java
new file mode 100644
index 000000000000..a8c4ac8cdeeb
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.PwleSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapter that splits Pwle segments with longer duration than the device capabilities.
+ *
+ * <p>This transformation replaces large {@link android.os.vibrator.PwleSegment} entries by a
+ * sequence of smaller segments that starts and ends at the same amplitudes/frequencies,
+ * interpolating the intermediate values.
+ *
+ * <p>The segments will not be changed if the device doesn't have
+ * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}.
+ */
+final class SplitPwleSegmentsAdapter implements VibrationSegmentsAdapter {
+
+ @Override
+ public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+ int repeatIndex) {
+ if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
+ // The vibrator does not have PWLE v2 capability, so keep the segments unchanged.
+ return repeatIndex;
+ }
+ int maxPwleDuration = (int) info.getMaxEnvelopeEffectDurationMillis();
+ if (maxPwleDuration <= 0) {
+ // No limit set to PWLE primitive duration.
+ return repeatIndex;
+ }
+
+ int segmentCount = segments.size();
+ for (int i = 0; i < segmentCount; i++) {
+ if (!(segments.get(i) instanceof PwleSegment pwleSegment)) {
+ continue;
+ }
+ int splits = ((int) pwleSegment.getDuration() + maxPwleDuration - 1) / maxPwleDuration;
+ if (splits <= 1) {
+ continue;
+ }
+ segments.remove(i);
+ segments.addAll(i, splitPwleSegment(pwleSegment, splits));
+ int addedSegments = splits - 1;
+ if (repeatIndex > i) {
+ repeatIndex += addedSegments;
+ }
+ i += addedSegments;
+ segmentCount += addedSegments;
+ }
+
+ return repeatIndex;
+ }
+
+ private static List<PwleSegment> splitPwleSegment(PwleSegment pwleSegment,
+ int splits) {
+ List<PwleSegment> pwleSegments = new ArrayList<>(splits);
+ float startFrequencyHz = pwleSegment.getStartFrequencyHz();
+ float endFrequencyHz = pwleSegment.getEndFrequencyHz();
+ long splitDuration = pwleSegment.getDuration() / splits;
+ float previousAmplitude = pwleSegment.getStartAmplitude();
+ float previousFrequencyHz = startFrequencyHz;
+ long accumulatedDuration = 0;
+
+ for (int i = 1; i < splits; i++) {
+ accumulatedDuration += splitDuration;
+ float durationRatio = (float) accumulatedDuration / pwleSegment.getDuration();
+ float interpolatedFrequency =
+ MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio);
+ float interpolatedAmplitude = MathUtils.lerp(pwleSegment.getStartAmplitude(),
+ pwleSegment.getEndAmplitude(), durationRatio);
+ PwleSegment pwleSplit = new PwleSegment(
+ previousAmplitude, interpolatedAmplitude,
+ previousFrequencyHz, interpolatedFrequency,
+ (int) splitDuration);
+ pwleSegments.add(pwleSplit);
+ previousAmplitude = pwleSplit.getEndAmplitude();
+ previousFrequencyHz = pwleSplit.getEndFrequencyHz();
+ }
+
+ pwleSegments.add(
+ new PwleSegment(previousAmplitude, pwleSegment.getEndAmplitude(),
+ previousFrequencyHz, endFrequencyHz,
+ (int) (pwleSegment.getDuration() - accumulatedDuration)));
+
+ return pwleSegments;
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
new file mode 100644
index 000000000000..1b6ce9dacfa9
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.os.CancellationSignal;
+import android.os.CombinedVibration;
+import android.os.ExternalVibration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.VibrationAttributes;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session started by a vendor request that can trigger {@link CombinedVibration}.
+ */
+final class VendorVibrationSession extends IVibrationSession.Stub
+ implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
+ private static final String TAG = "VendorVibrationSession";
+
+ /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
+ interface VibratorManagerHooks {
+
+ /** Tells the manager to end the vibration session. */
+ void endSession(long sessionId, boolean shouldAbort);
+
+ /**
+ * Tells the manager that the vibration session is finished and the vibrators can now be
+ * used for another vibration.
+ */
+ void onSessionReleased(long sessionId);
+
+ /** Request the manager to trigger a vibration within this session. */
+ void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration);
+ }
+
+ private final Object mLock = new Object();
+ private final long mSessionId = VibrationSession.nextSessionId();
+ private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport();
+ private final int[] mVibratorIds;
+ private final long mCreateUptime;
+ private final long mCreateTime; // for debugging
+ private final IVibrationSessionCallback mCallback;
+ private final CallerInfo mCallerInfo;
+ private final VibratorManagerHooks mManagerHooks;
+ private final DeviceAdapter mDeviceAdapter;
+ private final Handler mHandler;
+ private final List<DebugInfo> mVibrations = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private Status mStatus = Status.RUNNING;
+ @GuardedBy("mLock")
+ private Status mEndStatusRequest;
+ @GuardedBy("mLock")
+ private boolean mEndedByVendor;
+ @GuardedBy("mLock")
+ private long mStartTime; // for debugging
+ @GuardedBy("mLock")
+ private long mEndUptime;
+ @GuardedBy("mLock")
+ private long mEndTime; // for debugging
+ @GuardedBy("mLock")
+ private VibrationStepConductor mConductor;
+
+ VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler,
+ @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter,
+ @NonNull IVibrationSessionCallback callback) {
+ mCreateUptime = SystemClock.uptimeMillis();
+ mCreateTime = System.currentTimeMillis();
+ mVibratorIds = deviceAdapter.getAvailableVibratorIds();
+ mHandler = handler;
+ mCallback = callback;
+ mCallerInfo = callerInfo;
+ mManagerHooks = managerHooks;
+ mDeviceAdapter = deviceAdapter;
+ CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this);
+ }
+
+ @Override
+ public void vibrate(CombinedVibration vibration, String reason) {
+ CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid,
+ mCallerInfo.deviceId, mCallerInfo.opPkg, reason);
+ mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration);
+ }
+
+ @Override
+ public void finishSession() {
+ Slog.d(TAG, "Session finish requested, ending vibration session...");
+ // Do not abort session in HAL, wait for ongoing vibration requests to complete.
+ // This might take a while to end the session, but it can be aborted by cancelSession.
+ requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
+ }
+
+ @Override
+ public void cancelSession() {
+ Slog.d(TAG, "Session cancel requested, aborting vibration session...");
+ // Always abort session in HAL while cancelling it.
+ // This might be triggered after finishSession was already called.
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
+ }
+
+ @Override
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mCreateUptime;
+ }
+
+ @Override
+ public boolean isRepeating() {
+ return false;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mCallerInfo;
+ }
+
+ @Override
+ public IBinder getCallerToken() {
+ return mCallback.asBinder();
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() {
+ synchronized (mLock) {
+ return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
+ mEndUptime, mEndTime, mEndedByVendor, mVibrations);
+ }
+ }
+
+ @Override
+ public boolean wasEndRequested() {
+ synchronized (mLock) {
+ return mEndStatusRequest != null;
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.d(TAG, "Binder died, cancelling vibration session...");
+ requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
+ /* isVendorRequest= */ false);
+ }
+
+ @Override
+ public boolean linkToDeath() {
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error linking session to token death", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ try {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink session to token death", e);
+ }
+ }
+
+ @Override
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ // All requests to end a session should abort it to stop ongoing vibrations, even if
+ // immediate flag is false. Only the #finishSession API will not abort and wait for
+ // session vibrations to complete, which might take a long time.
+ requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false);
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ // Ignore it, the session vibration playback doesn't depend on HAL timings
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ // Ignore it, the session vibration playback doesn't depend on HAL timings
+ }
+
+ @Override
+ public void notifySessionCallback() {
+ synchronized (mLock) {
+ Slog.d(TAG, "Session callback received, ending vibration session...");
+ // If end was not requested then the HAL has cancelled the session.
+ maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
+ /* isVendorRequest= */ false);
+ maybeSetStatusToRequestedLocked();
+ clearVibrationConductor();
+ mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
+ + ", startTime: " + (mStartTime == 0 ? null : formatTime(mStartTime,
+ /* includeDate= */ true))
+ + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+ /* includeDate= */ true))
+ + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ + ", callerInfo: " + mCallerInfo
+ + ", vibratorIds: " + Arrays.toString(mVibratorIds)
+ + ", vibrations: " + mVibrations;
+ }
+ }
+
+ public Status getStatus() {
+ synchronized (mLock) {
+ return mStatus;
+ }
+ }
+
+ public boolean isStarted() {
+ synchronized (mLock) {
+ return mStartTime > 0;
+ }
+ }
+
+ public boolean isEnded() {
+ synchronized (mLock) {
+ return mStatus != Status.RUNNING;
+ }
+ }
+
+ public int[] getVibratorIds() {
+ return mVibratorIds;
+ }
+
+ @VisibleForTesting
+ public List<DebugInfo> getVibrations() {
+ synchronized (mLock) {
+ return new ArrayList<>(mVibrations);
+ }
+ }
+
+ public ICancellationSignal getCancellationSignal() {
+ return mCancellationSignal;
+ }
+
+ public void notifyStart() {
+ boolean isAlreadyEnded = false;
+ synchronized (mLock) {
+ if (isEnded()) {
+ // Session already ended, skip start callbacks.
+ isAlreadyEnded = true;
+ } else {
+ mStartTime = System.currentTimeMillis();
+ // Run client callback in separate thread.
+ mHandler.post(() -> {
+ try {
+ mCallback.onStarted(this);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying vendor session started", e);
+ }
+ });
+ }
+ }
+ if (isAlreadyEnded) {
+ // Session already ended, make sure we end it in the HAL.
+ mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true));
+ }
+ }
+
+ public void notifyVibrationAttempt(DebugInfo vibrationDebugInfo) {
+ mVibrations.add(vibrationDebugInfo);
+ }
+
+ @Nullable
+ public VibrationStepConductor clearVibrationConductor() {
+ synchronized (mLock) {
+ VibrationStepConductor conductor = mConductor;
+ if (conductor != null) {
+ mVibrations.add(conductor.getVibration().getDebugInfo());
+ }
+ mConductor = null;
+ return conductor;
+ }
+ }
+
+ public DeviceAdapter getDeviceAdapter() {
+ return mDeviceAdapter;
+ }
+
+ public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) {
+ synchronized (mLock) {
+ if (mConductor != null) {
+ Slog.d(TAG, "Vibration session still dispatching previous vibration,"
+ + " new vibration ignored");
+ return false;
+ }
+ mConductor = conductor;
+ return true;
+ }
+ }
+
+ private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
+ boolean shouldTriggerSessionHook = false;
+ synchronized (mLock) {
+ maybeSetEndRequestLocked(status, isVendorRequest);
+ if (isStarted()) {
+ // Always trigger session hook after it has started, in case new request aborts an
+ // already finishing session. Wait for HAL callback before actually ending here.
+ shouldTriggerSessionHook = true;
+ } else {
+ // Session did not start in the HAL, end it right away.
+ maybeSetStatusToRequestedLocked();
+ }
+ }
+ if (shouldTriggerSessionHook) {
+ mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort));
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
+ if (mEndStatusRequest != null) {
+ // End already requested, keep first requested status and time.
+ return;
+ }
+ mEndStatusRequest = status;
+ mEndedByVendor = isVendorRequest;
+ mEndTime = System.currentTimeMillis();
+ mEndUptime = SystemClock.uptimeMillis();
+ if (mConductor != null) {
+ // Vibration is being dispatched when session end was requested, cancel it.
+ mConductor.notifyCancelled(new Vibration.EndInfo(status),
+ /* immediate= */ status != Status.FINISHED);
+ }
+ if (isStarted()) {
+ // Only trigger "finishing" callback if session started.
+ // Run client callback in separate thread.
+ mHandler.post(() -> {
+ try {
+ mCallback.onFinishing();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying vendor session is finishing", e);
+ }
+ });
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void maybeSetStatusToRequestedLocked() {
+ if (isEnded()) {
+ // End already set, keep first requested status and time.
+ return;
+ }
+ if (mEndStatusRequest == null) {
+ // No end status was requested, nothing to set.
+ return;
+ }
+ mStatus = mEndStatusRequest;
+ // Run client callback in separate thread.
+ final Status endStatus = mStatus;
+ mHandler.post(() -> {
+ try {
+ mCallback.onFinished(toSessionStatus(endStatus));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying vendor session is finishing", e);
+ }
+ });
+ }
+
+ @android.os.vibrator.VendorVibrationSession.Status
+ private static int toSessionStatus(Status status) {
+ // Exhaustive switch to cover all possible internal status.
+ return switch (status) {
+ case FINISHED
+ -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS;
+ case IGNORED_UNSUPPORTED
+ -> STATUS_UNSUPPORTED;
+ case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER,
+ CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF,
+ CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON
+ -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED;
+ case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING,
+ IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE,
+ IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED,
+ IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER
+ -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED;
+ case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING,
+ IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, FINISHED_UNEXPECTED, RUNNING
+ -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR;
+ };
+ }
+
+ /**
+ * Holds lightweight debug information about the session that could potentially be kept in
+ * memory for a long time for bugreport dumpsys operations.
+ *
+ * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
+ * potentially expensive or resource-linked objects, such as {@link IBinder}.
+ */
+ static final class DebugInfoImpl implements VibrationSession.DebugInfo {
+ private final Status mStatus;
+ private final CallerInfo mCallerInfo;
+ private final List<DebugInfo> mVibrations;
+
+ private final long mCreateUptime;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
+ private final long mDurationMs;
+ private final boolean mEndedByVendor;
+
+ DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
+ long startTime, long endUptime, long endTime, boolean endedByVendor,
+ List<DebugInfo> vibrations) {
+ mStatus = status;
+ mCallerInfo = callerInfo;
+ mCreateUptime = createUptime;
+ mCreateTime = createTime;
+ mStartTime = startTime;
+ mEndTime = endTime;
+ mEndedByVendor = endedByVendor;
+ mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
+ mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
+ }
+
+ @Override
+ public Status getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mCreateUptime;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mCallerInfo;
+ }
+
+ @Nullable
+ @Override
+ public Object getDumpAggregationKey() {
+ return null; // No aggregation.
+ }
+
+ @Override
+ public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ if (mStartTime > 0) {
+ // Only log sessions that have started.
+ statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
+ statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
+ mVibrations.size());
+ if (!mEndedByVendor) {
+ statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid);
+ }
+ }
+ for (DebugInfo vibration : mVibrations) {
+ vibration.logMetrics(statsLogger);
+ }
+ }
+
+ @Override
+ public void dump(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(VibrationProto.END_TIME, mEndTime);
+ proto.write(VibrationProto.DURATION_MS, mDurationMs);
+ proto.write(VibrationProto.STATUS, mStatus.ordinal());
+
+ final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
+ final VibrationAttributes attrs = mCallerInfo.attrs;
+ proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
+ proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
+ proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
+ proto.end(attrsToken);
+
+ proto.end(token);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("VibrationSession:");
+ pw.increaseIndent();
+ pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
+ pw.println("durationMs = " + mDurationMs);
+ pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true));
+ pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true));
+ pw.println("endTime = " + (mEndTime == 0 ? null
+ : formatTime(mEndTime, /*includeDate=*/ true)));
+ pw.println("callerInfo = " + mCallerInfo);
+
+ pw.println("vibrations:");
+ pw.increaseIndent();
+ for (DebugInfo vibration : mVibrations) {
+ vibration.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+
+ @Override
+ public void dumpCompact(IndentingPrintWriter pw) {
+ // Follow pattern from Vibration.DebugInfoImpl for better debugging from dumpsys.
+ String timingsStr = String.format(Locale.ROOT,
+ "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
+ formatTime(mCreateTime, /*includeDate=*/ true),
+ "session",
+ mStatus.name().toLowerCase(Locale.ROOT),
+ mDurationMs,
+ mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false),
+ mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false));
+ String paramStr = String.format(Locale.ROOT,
+ " | flags: %4s | usage: %s",
+ Long.toBinaryString(mCallerInfo.attrs.getFlags()),
+ mCallerInfo.attrs.usageToString());
+ // Optional, most vibrations should not be defined via AudioAttributes
+ // so skip them to simplify the logs
+ String audioUsageStr =
+ mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN
+ ? " | audioUsage=" + AudioAttributes.usageToString(
+ mCallerInfo.attrs.getOriginalAudioUsage())
+ : "";
+ String callerStr = String.format(Locale.ROOT,
+ " | %s (uid=%d, deviceId=%d) | reason: %s",
+ mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
+ pw.println(timingsStr + paramStr + audioUsageStr + callerStr);
+
+ pw.increaseIndent();
+ for (DebugInfo vibration : mVibrations) {
+ vibration.dumpCompact(pw);
+ }
+ pw.decreaseIndent();
+ }
+
+ @Override
+ public String toString() {
+ return "createTime: " + formatTime(mCreateTime, /* includeDate= */ true)
+ + ", startTime: " + formatTime(mStartTime, /* includeDate= */ true)
+ + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+ /* includeDate= */ true))
+ + ", durationMs: " + mDurationMs
+ + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ + ", callerInfo: " + mCallerInfo
+ + ", vibrations: " + mVibrations;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 9a0479337471..2bf44981e6d5 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioAttributes;
@@ -23,6 +25,7 @@ import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -31,9 +34,6 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -42,27 +42,19 @@ import java.util.concurrent.atomic.AtomicInteger;
* The base class for all vibrations.
*/
abstract class Vibration {
- private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "HH:mm:ss.SSS");
- private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "MM-dd HH:mm:ss.SSS");
-
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
public final long id;
public final VibrationSession.CallerInfo callerInfo;
public final VibrationStats stats = new VibrationStats();
- public final IBinder callerToken;
private VibrationSession.Status mStatus;
- Vibration(@NonNull IBinder token, @NonNull VibrationSession.CallerInfo callerInfo) {
- Objects.requireNonNull(token);
+ Vibration(@NonNull VibrationSession.CallerInfo callerInfo) {
Objects.requireNonNull(callerInfo);
mStatus = VibrationSession.Status.RUNNING;
this.id = sNextVibrationId.getAndIncrement();
- this.callerToken = token;
this.callerInfo = callerInfo;
}
@@ -91,15 +83,9 @@ abstract class Vibration {
stats.reportEnded(endInfo.endedBy);
}
- /** Return true if vibration is a repeating vibration. */
- abstract boolean isRepeating();
-
/** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
abstract VibrationSession.DebugInfo getDebugInfo();
- /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
- abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
-
/** Immutable info passed as a signal to end a vibration. */
static final class EndInfo {
/** The vibration status to be set when it ends with this info. */
@@ -149,35 +135,41 @@ abstract class Vibration {
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfoImpl implements VibrationSession.DebugInfo {
- final VibrationSession.Status mStatus;
- final long mCreateTime;
- final VibrationSession.CallerInfo mCallerInfo;
+ private final VibrationSession.Status mStatus;
+ private final VibrationStats.StatsInfo mStatsInfo;
+ private final VibrationSession.CallerInfo mCallerInfo;
@Nullable
- final CombinedVibration mPlayedEffect;
-
- private final long mStartTime;
- private final long mEndTime;
- private final long mDurationMs;
+ private final CombinedVibration mPlayedEffect;
@Nullable
private final CombinedVibration mOriginalEffect;
private final int mScaleLevel;
private final float mAdaptiveScale;
- DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
- @Nullable CombinedVibration playedEffect,
- @Nullable CombinedVibration originalEffect, int scaleLevel,
- float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
+ private final long mCreateUptime;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
+ private final long mDurationMs;
+
+ DebugInfoImpl(VibrationSession.Status status,
+ @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType,
+ VibrationStats stats, @Nullable CombinedVibration playedEffect,
+ @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) {
Objects.requireNonNull(callerInfo);
- mCreateTime = stats.getCreateTimeDebug();
- mStartTime = stats.getStartTimeDebug();
- mEndTime = stats.getEndTimeDebug();
- mDurationMs = stats.getDurationDebug();
+ mCallerInfo = callerInfo;
+ mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType,
+ callerInfo.attrs.getUsage(), status);
mPlayedEffect = playedEffect;
mOriginalEffect = originalEffect;
mScaleLevel = scaleLevel;
mAdaptiveScale = adaptiveScale;
- mCallerInfo = callerInfo;
mStatus = status;
+
+ mCreateUptime = stats.getCreateUptimeMillis();
+ mCreateTime = stats.getCreateTimeDebug();
+ mStartTime = stats.getStartTimeDebug();
+ mEndTime = stats.getEndTimeDebug();
+ mDurationMs = stats.getDurationDebug();
}
@Override
@@ -187,7 +179,7 @@ abstract class Vibration {
@Override
public long getCreateUptimeMillis() {
- return mCreateTime;
+ return mCreateUptime;
}
@Override
@@ -219,6 +211,12 @@ abstract class Vibration {
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+ statsLogger.writeVibrationReportedAsync(mStatsInfo);
+ if (Flags.vendorVibrationEffects()) {
+ // Log effect as it was originally requested.
+ statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid,
+ mOriginalEffect != null ? mOriginalEffect : mPlayedEffect);
+ }
}
@Override
@@ -401,12 +399,5 @@ abstract class Vibration {
proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
proto.end(token);
}
-
- private String formatTime(long timeInMillis, boolean includeDate) {
- return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
- // Ensure timezone is retrieved at formatting time
- .withZone(ZoneId.systemDefault())
- .format(Instant.ofEpochMilli(timeInMillis));
- }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSegmentsValidator.java b/services/core/java/com/android/server/vibrator/VibrationSegmentsValidator.java
new file mode 100644
index 000000000000..75002bf3e0b5
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationSegmentsValidator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.VibratorInfo;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/** Validates a sequence of {@link VibrationEffectSegment}s for a vibrator. */
+public interface VibrationSegmentsValidator {
+ /**
+ * Checks whether the vibrator can play the provided segments based on the given
+ * {@link VibratorInfo}.
+ *
+ * @param info The vibrator info to be applied to the sequence of segments.
+ * @param segments List of {@link VibrationEffectSegment} to be checked.
+ * @return True if vibrator can play the effect, false otherwise.
+ */
+ boolean hasValidSegments(VibratorInfo info, List<VibrationEffectSegment> segments);
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index 5640b49b28d8..ae95a70e2a4f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -25,7 +25,11 @@ import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a generic vibration session that plays one or more vibration requests.
@@ -39,24 +43,88 @@ import java.util.Objects;
*/
interface VibrationSession {
+ // Used to generate globally unique session ids.
+ AtomicInteger sNextSessionId = new AtomicInteger(1); // 0 = no callback
+
+ static long nextSessionId() {
+ return sNextSessionId.getAndIncrement();
+ }
+
+ /** Returns the session id. */
+ long getSessionId();
+
+ /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+ long getCreateUptimeMillis();
+
+ /** Return true if vibration session plays a repeating vibration. */
+ boolean isRepeating();
+
/** Returns data about the client app that triggered this vibration session. */
CallerInfo getCallerInfo();
+ /** Returns the binder token from the client app attached to this vibration session. */
+ IBinder getCallerToken();
+
/** Returns debug data for logging and metric reports. */
DebugInfo getDebugInfo();
+ /** Links this session to the app process death, returning false if link failed. */
+ boolean linkToDeath();
+
+ /** Removes link to the app process death. */
+ void unlinkToDeath();
+
+ /** Returns true if this session was requested to end by {@link #requestEnd}. */
+ boolean wasEndRequested();
+
/**
- * Links this session to the app process death with given callback to handle it.
+ * Request the end of this session, which might be acted upon asynchronously.
*
- * <p>This can be used by the service to end the vibration session when the app process dies.
+ * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no
+ * {@link CallerInfo} and with {@code immediate} flag set to false.
*/
- void linkToDeath(Runnable callback);
+ default void requestEnd(@NonNull Status status) {
+ requestEnd(status, /* endedBy= */ null, /* immediate= */ false);
+ }
- /** Removes link to the app process death. */
- void unlinkToDeath();
+ /**
+ * Notify the session end was requested, which might be acted upon asynchronously.
+ *
+ * <p>Only the first end signal will be used to end a session, but subsequent calls with
+ * {@code immediate} flag set to true can still force it to take effect urgently.
+ *
+ * @param status the end status.
+ * @param endedBy the {@link CallerInfo} of the session that requested this session to end.
+ * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
+ */
+ void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate);
+
+ /**
+ * Notify a vibrator has completed the last command during the playback of given vibration.
+ *
+ * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is
+ * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
+ * since its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifyVibratorCallback(int vibratorId, long vibrationId);
+
+ /**
+ * Notify all synced vibrators have completed the last synchronized command during the playback
+ * of given vibration.
+ *
+ * <p>This will be called by the vibrator manager hardware callback indicating the last
+ * synchronized vibrate call is complete. This does not mean the vibration is complete, since
+ * its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifySyncedVibratorsCallback(long vibrationId);
- /** Notify the session end was requested, which might be acted upon asynchronously. */
- void notifyEnded();
+ /**
+ * Notify vibrator manager have completed the vibration session.
+ *
+ * <p>This will be called by the vibrator manager hardware callback indicating the session
+ * is complete, either because it was ended or cancelled by the service or the vendor.
+ */
+ void notifySessionCallback();
/**
* Session status with reference to values from vibratormanagerservice.proto for logging.
@@ -119,7 +187,7 @@ interface VibrationSession {
public final String reason;
CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
- String reason) {
+ @Nullable String reason) {
Objects.requireNonNull(attrs);
this.attrs = attrs;
this.uid = uid;
@@ -166,6 +234,17 @@ interface VibrationSession {
*/
interface DebugInfo {
+ DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+ DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "MM-dd HH:mm:ss.SSS");
+
+ static String formatTime(long timeInMillis, boolean includeDate) {
+ return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
+ // Ensure timezone is retrieved at formatting time
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(timeInMillis));
+ }
+
/** Return the vibration session status. */
Status getStatus();
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 102306fa3c42..797a350e4395 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -197,7 +197,7 @@ final class VibrationSettings {
@GuardedBy("mLock")
private boolean mVibrateOn;
@GuardedBy("mLock")
- private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
+ private int mRingerMode;
@GuardedBy("mLock")
private boolean mOnWirelessCharger;
@@ -617,11 +617,11 @@ final class VibrationSettings {
private void updateRingerMode() {
synchronized (mLock) {
- // If audio manager was not loaded yet then assume normal mode.
- // This will be loaded again as soon as the audio manager is loaded in onSystemReady.
- mRingerMode = (mAudioManager == null)
- ? AudioManager.RINGER_MODE_NORMAL
- : mAudioManager.getRingerModeInternal();
+ if (mAudioManager == null) {
+ // Service not ready yet or audio service not available, skip this update request.
+ return;
+ }
+ mRingerMode = mAudioManager.getRingerModeInternal();
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index fc0c6e7bf05e..de423f061b2b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -18,9 +18,11 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.SystemClock;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -37,11 +39,11 @@ final class VibrationStats {
// vibrate request.
// - Start: time a vibration started to play, which is closer to the time that the
// VibrationEffect started playing the very first segment.
- // - End: time a vibration ended, even if it never started to play. This can be as soon as the
- // vibrator HAL reports it has finished the last command, or before it has even started
- // when the vibration is ignored or cancelled.
- // Create and end times set by VibratorManagerService only, guarded by its lock.
- // Start times set by VibrationThread only (single-threaded).
+ // - End: time a vibration ended with a status, even if it never started to play. This can be as
+ // soon as the vibrator HAL reports it has finished the last command, or before it has
+ // even started when the vibration is ignored or cancelled.
+ // Created and ended times set by VibratorManagerService only, guarded by its lock.
+ // Start time set by VibrationThread only (single-threaded).
private long mCreateUptimeMillis;
private long mStartUptimeMillis;
private long mEndUptimeMillis;
@@ -97,6 +99,10 @@ final class VibrationStats {
mInterruptedUsage = -1;
}
+ StatsInfo toStatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status) {
+ return new VibrationStats.StatsInfo(uid, vibrationType, usage, status, this);
+ }
+
long getCreateUptimeMillis() {
return mCreateUptimeMillis;
}
@@ -287,6 +293,29 @@ final class VibrationStats {
}
}
+ /** Report a call to vibrator method to trigger a vibration as a PWLE. */
+ void reportComposePwle(long halResult, PwlePoint[] pwlePoints) {
+ mVibratorComposePwleCount++;
+ mVibrationPwleTotalSize += pwlePoints.length;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ // Remove the zero-amplitude segments to update the total time the vibrator was ON.
+ for (int i = 0; i < pwlePoints.length - 1; i++) {
+ PwlePoint current = pwlePoints[i];
+ PwlePoint next = pwlePoints[i + 1];
+
+ if (current.getAmplitude() == 0 && next.getAmplitude() == 0) {
+ halResult -= next.getTimeMillis();
+ }
+ }
+
+ if (halResult > 0) {
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ }
+ }
+
/**
* Increment the stats for total number of times the {@code setExternalControl} method was
* triggered in the vibrator HAL.
@@ -300,7 +329,7 @@ final class VibrationStats {
* {@link com.android.internal.util.FrameworkStatsLog} as a
* {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
*/
- static final class StatsInfo {
+ public static final class StatsInfo {
public final int uid;
public final int vibrationType;
public final int usage;
@@ -331,7 +360,7 @@ final class VibrationStats {
private boolean mIsWritten;
StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
- VibrationStats stats, long completionUptimeMillis) {
+ VibrationStats stats) {
this.uid = uid;
this.vibrationType = vibrationType;
this.usage = usage;
@@ -342,6 +371,9 @@ final class VibrationStats {
interruptedUsage = stats.mInterruptedUsage;
repeatCount = stats.mRepeatCount;
+ // Consider this vibration is being completed now.
+ long completionUptimeMillis = SystemClock.uptimeMillis();
+
// This duration goes from the time this object was created until the time it was
// completed. We can use latencies to detect the times between first and last
// interaction with vibrator.
@@ -419,5 +451,25 @@ final class VibrationStats {
}
return res;
}
+
+ /**
+ * Returns the vibration type value from {@code ReportedVibration} that best represents this
+ * {@link CombinedVibration}.
+ *
+ * <p>This does not include external vibrations, as those are not represented by a single
+ * vibration effect.
+ */
+ public static int findVibrationType(@Nullable CombinedVibration effect) {
+ if (effect == null) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
+ if (effect.hasVendorEffects()) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR;
+ }
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED;
+ }
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 5137d1938332..1e20debe156d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -20,11 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.CombinedVibration;
-import android.os.IBinder;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwleSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.IntArray;
@@ -53,7 +53,7 @@ import java.util.concurrent.TimeoutException;
* VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
* methods (which should never be used from the VibrationThread thread).
*/
-final class VibrationStepConductor implements IBinder.DeathRecipient {
+final class VibrationStepConductor {
private static final boolean DEBUG = VibrationThread.DEBUG;
private static final String TAG = VibrationThread.TAG;
@@ -69,6 +69,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
// Used within steps.
public final VibrationSettings vibrationSettings;
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+ public final boolean isInSession;
private final DeviceAdapter mDeviceAdapter;
private final VibrationScaler mVibrationScaler;
@@ -105,12 +106,13 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
private int mRemainingStartSequentialEffectSteps;
private int mSuccessfulVibratorOnSteps;
- VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
- DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
- VibratorFrameworkStatsLogger statsLogger,
+ VibrationStepConductor(HalVibration vib, boolean isInSession,
+ VibrationSettings vibrationSettings, DeviceAdapter deviceAdapter,
+ VibrationScaler vibrationScaler, VibratorFrameworkStatsLogger statsLogger,
CompletableFuture<Void> requestVibrationParamsFuture,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
this.mVibration = vib;
+ this.isInSession = isInSession;
this.vibrationSettings = vibrationSettings;
this.mDeviceAdapter = deviceAdapter;
mVibrationScaler = vibrationScaler;
@@ -167,12 +169,20 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
pendingVibratorOffDeadline);
}
+ if (segment instanceof PwleSegment) {
+ return new ComposePwleV2VibratorStep(this, startTime, controller, effect,
+ segmentIndex, pendingVibratorOffDeadline);
+ }
return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
pendingVibratorOffDeadline);
}
- /** Called when this conductor is going to be started running by the VibrationThread. */
- public void prepareToStart() {
+ /**
+ * Called when this conductor is going to be started running by the VibrationThread.
+ *
+ * @return True if the vibration effect can be played, false otherwise.
+ */
+ public boolean prepareToStart() {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
@@ -183,7 +193,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
// Scale resolves the default amplitudes from the effect before scaling them.
mVibration.scaleEffects(mVibrationScaler);
- mVibration.adaptToDevice(mDeviceAdapter);
+ if (!mVibration.adaptToDevice(mDeviceAdapter)) {
+ // Unable to adapt vibration effect for playback. This likely indicates the presence
+ // of unsupported segments. The original effect will be ignored.
+ return false;
+ }
CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
mPendingVibrateSteps++;
// This count is decremented at the completion of the step, so we don't subtract one.
@@ -192,6 +206,8 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
// Vibration will start playing in the Vibrator, following the effect timings and delays.
// Report current time as the vibration start time, for debugging.
mVibration.stats.reportStarted();
+
+ return true;
}
public HalVibration getVibration() {
@@ -272,6 +288,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
if (nextStep == null) {
return true; // Finished
}
+ if (isInSession) {
+ return true; // Don't wait to play session vibration steps
+ }
long waitMillis = nextStep.calculateWaitTime();
if (waitMillis <= 0) {
return true; // Regular step ready
@@ -344,20 +363,6 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
/**
- * Binder death notification. VibrationThread registers this when it's running a conductor.
- * Note that cancellation could theoretically happen immediately, before the conductor has
- * started, but in this case it will be processed in the first signals loop.
- */
- @Override
- public void binderDied() {
- if (DEBUG) {
- Slog.d(TAG, "Binder died, cancelling vibration...");
- }
- notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
- /* immediate= */ false);
- }
-
- /**
* Notify the execution that cancellation is requested. This will be acted upon
* asynchronously in the VibrationThread.
*
@@ -452,6 +457,23 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
}
+ /**
+ * Notify that the VibrationThread has completed the vibration effect playback.
+ *
+ * <p>This is a lightweight method intended to be called by the vibration thread directly. The
+ * VibrationThread may still be continuing with cleanup tasks, and should not be given new work
+ * until it notifies the manager that it has been released.
+ */
+ public void notifyVibrationComplete(@NonNull Vibration.EndInfo endInfo) {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration " + mVibration.id + " finished with " + endInfo);
+ }
+ mVibration.end(endInfo);
+ }
+
/** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */
public boolean wasNotifiedToCancel() {
if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 4c1e16c0d14e..cb9988fd698e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,12 +16,12 @@
package com.android.server.vibrator;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.WorkSource;
@@ -31,7 +31,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.vibrator.VibrationSession.Status;
-import java.util.NoSuchElementException;
import java.util.Objects;
/** Plays a {@link HalVibration} in dedicated thread. */
@@ -72,14 +71,6 @@ final class VibrationThread extends Thread {
void noteVibratorOff(int uid);
/**
- * Tell the manager that the currently active vibration has completed its vibration, from
- * the perspective of the Effect. However, the VibrationThread may still be continuing with
- * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
- * is called.
- */
- void onVibrationCompleted(long vibrationId, @NonNull Vibration.EndInfo vibrationEndInfo);
-
- /**
* Tells the manager that the VibrationThread is finished with the previous vibration and
* all of its cleanup tasks, and the vibrators can now be used for another vibration.
*/
@@ -128,7 +119,7 @@ final class VibrationThread extends Thread {
* before the release callback.
*/
boolean runVibrationOnVibrationThread(VibrationStepConductor conductor) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrationOnVibrationThread");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "runVibrationOnVibrationThread");
try {
synchronized (mLock) {
if (mRequestedActiveConductor != null) {
@@ -140,7 +131,7 @@ final class VibrationThread extends Thread {
}
return true;
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -243,7 +234,7 @@ final class VibrationThread extends Thread {
mWakeLock.acquire();
try {
try {
- runCurrentVibrationWithWakeLockAndDeathLink();
+ playVibration();
} finally {
clientVibrationCompleteIfNotAlready(
new Vibration.EndInfo(Status.FINISHED_UNEXPECTED));
@@ -254,48 +245,33 @@ final class VibrationThread extends Thread {
}
}
- /**
- * Runs the VibrationThread with the binder death link, handling link/unlink failures.
- * Called from within runWithWakeLock.
- */
- private void runCurrentVibrationWithWakeLockAndDeathLink() {
- IBinder vibrationBinderToken = mExecutingConductor.getVibration().callerToken;
- try {
- vibrationBinderToken.linkToDeath(mExecutingConductor, 0);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error linking vibration to token death", e);
- clientVibrationCompleteIfNotAlready(
- new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN));
- return;
- }
- // Ensure that the unlink always occurs now.
- try {
- // This is the actual execution of the vibration.
- playVibration();
- } finally {
- try {
- vibrationBinderToken.unlinkToDeath(mExecutingConductor, 0);
- } catch (NoSuchElementException e) {
- Slog.wtf(TAG, "Failed to unlink token", e);
- }
- }
- }
-
// Indicate that the vibration is complete. This can be called multiple times only for
// convenience of handling error conditions - an error after the client is complete won't
// affect the status.
private void clientVibrationCompleteIfNotAlready(@NonNull Vibration.EndInfo vibrationEndInfo) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
- mVibratorManagerHooks.onVibrationCompleted(
- mExecutingConductor.getVibration().id, vibrationEndInfo);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "notifyVibrationComplete");
+ try {
+ mExecutingConductor.notifyVibrationComplete(vibrationEndInfo);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
}
}
private void playVibration() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "playVibration");
try {
- mExecutingConductor.prepareToStart();
+ if (!mExecutingConductor.prepareToStart()) {
+ // The effect cannot be played, start clean-up tasks and notify
+ // callback immediately.
+ clientVibrationCompleteIfNotAlready(
+ new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED));
+
+ return;
+ }
+
while (!mExecutingConductor.isFinished()) {
boolean readyToRun = mExecutingConductor.waitUntilNextStepIsDue();
// If we waited, don't run the next step, but instead re-evaluate status.
@@ -317,7 +293,7 @@ final class VibrationThread extends Thread {
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 3c478500876f..acb31ceb4027 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
+
import android.annotation.Nullable;
import android.hardware.vibrator.IVibrator;
import android.os.Binder;
@@ -28,6 +30,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -55,8 +58,7 @@ final class VibratorController {
// for a snippet of the current known vibrator state/info.
private volatile VibratorInfo mVibratorInfo;
private volatile boolean mVibratorInfoLoadSuccessful;
- private volatile boolean mIsVibrating;
- private volatile boolean mIsUnderExternalControl;
+ private volatile VibratorState mCurrentState;
private volatile float mCurrentAmplitude;
/**
@@ -73,6 +75,11 @@ final class VibratorController {
void onComplete(int vibratorId, long vibrationId);
}
+ /** Representation of the vibrator state based on the interactions through this controller. */
+ private enum VibratorState {
+ IDLE, VIBRATING, UNDER_EXTERNAL_CONTROL
+ }
+
VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
this(vibratorId, listener, new NativeWrapper());
}
@@ -85,6 +92,7 @@ final class VibratorController {
VibratorInfo.Builder vibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
mVibratorInfoLoadSuccessful = mNativeWrapper.getInfo(vibratorInfoBuilder);
mVibratorInfo = vibratorInfoBuilder.build();
+ mCurrentState = VibratorState.IDLE;
if (!mVibratorInfoLoadSuccessful) {
Slog.e(TAG,
@@ -104,7 +112,7 @@ final class VibratorController {
return false;
}
// Notify its callback after new client registered.
- notifyStateListener(listener, mIsVibrating);
+ notifyStateListener(listener, isVibrating(mCurrentState));
}
return true;
} finally {
@@ -124,7 +132,7 @@ final class VibratorController {
/** Reruns the query to the vibrator to load the {@link VibratorInfo}, if not yet successful. */
public void reloadVibratorInfoIfNeeded() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#reloadVibratorInfoIfNeeded");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#reloadVibratorInfoIfNeeded");
try {
// Early check outside lock, for quick return.
if (mVibratorInfoLoadSuccessful) {
@@ -143,7 +151,7 @@ final class VibratorController {
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -164,7 +172,7 @@ final class VibratorController {
* automatically notified to any registered {@link IVibratorStateListener} on change.
*/
public boolean isVibrating() {
- return mIsVibrating;
+ return isVibrating(mCurrentState);
}
/**
@@ -182,11 +190,6 @@ final class VibratorController {
return mCurrentAmplitude;
}
- /** Return {@code true} if this vibrator is under external control, false otherwise. */
- public boolean isUnderExternalControl() {
- return mIsUnderExternalControl;
- }
-
/**
* Check against this vibrator capabilities.
*
@@ -199,33 +202,37 @@ final class VibratorController {
/** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
public boolean isAvailable() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#isAvailable");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#isAvailable");
try {
synchronized (mLock) {
return mNativeWrapper.isAvailable();
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
/**
* Set the vibrator control to be external or not, based on given flag.
*
- * <p>This will affect the state of {@link #isUnderExternalControl()}.
+ * <p>This will affect the state of {@link #isVibrating()}.
*/
public void setExternalControl(boolean externalControl) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setExternalControl(" + externalControl + ")");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR,
+ externalControl ? "VibratorController#enableExternalControl"
+ : "VibratorController#disableExternalControl");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
return;
}
+ VibratorState newState =
+ externalControl ? VibratorState.UNDER_EXTERNAL_CONTROL : VibratorState.IDLE;
synchronized (mLock) {
- mIsUnderExternalControl = externalControl;
mNativeWrapper.setExternalControl(externalControl);
+ updateStateAndNotifyListenersLocked(newState);
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -234,7 +241,7 @@ final class VibratorController {
* if given {@code effect} is {@code null}.
*/
public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#updateAlwaysOn");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#updateAlwaysOn");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
return;
@@ -248,24 +255,24 @@ final class VibratorController {
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
/** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
public void setAmplitude(float amplitude) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#setAmplitude");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#setAmplitude");
try {
synchronized (mLock) {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
mNativeWrapper.setAmplitude(amplitude);
}
- if (mIsVibrating) {
+ if (mCurrentState == VibratorState.VIBRATING) {
mCurrentAmplitude = amplitude;
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -279,18 +286,18 @@ final class VibratorController {
* do not support the input or a negative number if the operation failed.
*/
public long on(long milliseconds, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on");
try {
synchronized (mLock) {
long duration = mNativeWrapper.on(milliseconds, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -304,7 +311,7 @@ final class VibratorController {
* do not support the input or a negative number if the operation failed.
*/
public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
synchronized (mLock) {
Parcel vendorData = Parcel.obtain();
try {
@@ -315,12 +322,12 @@ final class VibratorController {
vendorEffect.getAdaptiveScale(), vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
} finally {
vendorData.recycle();
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
@@ -335,19 +342,19 @@ final class VibratorController {
* do not support the input or a negative number if the operation failed.
*/
public long on(PrebakedSegment prebaked, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
try {
synchronized (mLock) {
long duration = mNativeWrapper.perform(prebaked.getEffectId(),
prebaked.getEffectStrength(), vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -361,7 +368,7 @@ final class VibratorController {
* do not support the input or a negative number if the operation failed.
*/
public long on(PrimitiveSegment[] primitives, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return 0;
@@ -370,12 +377,12 @@ final class VibratorController {
long duration = mNativeWrapper.compose(primitives, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -388,7 +395,7 @@ final class VibratorController {
* @return The duration of the effect playing, or 0 if unsupported.
*/
public long on(RampSegment[] primitives, long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
return 0;
@@ -398,12 +405,39 @@ final class VibratorController {
long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
+ }
+ return duration;
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ /**
+ * Plays a composition of pwle v2 points, using {@code vibrationId} for completion callback
+ * to {@link OnVibrationCompleteListener}.
+ *
+ * <p>This will affect the state of {@link #isVibrating()}.
+ *
+ * @return The duration of the effect playing, or 0 if unsupported.
+ */
+ public long on(PwlePoint[] pwlePoints, long vibrationId) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
+ try {
+ if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
+ return 0;
+ }
+ synchronized (mLock) {
+ long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
+ if (duration > 0) {
+ mCurrentAmplitude = -1;
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -413,15 +447,15 @@ final class VibratorController {
* <p>This will affect the state of {@link #isVibrating()}.
*/
public void off() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorController#off");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#off");
try {
synchronized (mLock) {
mNativeWrapper.off();
mCurrentAmplitude = 0;
- notifyListenerOnVibrating(false);
+ updateStateAndNotifyListenersLocked(VibratorState.IDLE);
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -439,9 +473,8 @@ final class VibratorController {
return "VibratorController{"
+ "mVibratorInfo=" + mVibratorInfo
+ ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful
- + ", mIsVibrating=" + mIsVibrating
+ + ", mCurrentState=" + mCurrentState.name()
+ ", mCurrentAmplitude=" + mCurrentAmplitude
- + ", mIsUnderExternalControl=" + mIsUnderExternalControl
+ ", mVibratorStateListeners count="
+ mVibratorStateListeners.getRegisteredCallbackCount()
+ '}';
@@ -450,8 +483,7 @@ final class VibratorController {
void dump(IndentingPrintWriter pw) {
pw.println("Vibrator (id=" + mVibratorInfo.getId() + "):");
pw.increaseIndent();
- pw.println("isVibrating = " + mIsVibrating);
- pw.println("isUnderExternalControl = " + mIsUnderExternalControl);
+ pw.println("currentState = " + mCurrentState.name());
pw.println("currentAmplitude = " + mCurrentAmplitude);
pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful);
pw.println("vibratorStateListener size = "
@@ -460,14 +492,19 @@ final class VibratorController {
pw.decreaseIndent();
}
+ /**
+ * Updates current vibrator state and notify listeners if {@link #isVibrating()} result changed.
+ */
@GuardedBy("mLock")
- private void notifyListenerOnVibrating(boolean isVibrating) {
- if (mIsVibrating != isVibrating) {
- mIsVibrating = isVibrating;
+ private void updateStateAndNotifyListenersLocked(VibratorState state) {
+ boolean previousIsVibrating = isVibrating(mCurrentState);
+ final boolean newIsVibrating = isVibrating(state);
+ mCurrentState = state;
+ if (previousIsVibrating != newIsVibrating) {
// The broadcast method is safe w.r.t. register/unregister listener methods, but lock
// is required here to guarantee delivery order.
mVibratorStateListeners.broadcast(
- listener -> notifyStateListener(listener, isVibrating));
+ listener -> notifyStateListener(listener, newIsVibrating));
}
}
@@ -479,6 +516,11 @@ final class VibratorController {
}
}
+ /** Returns true only if given state is not {@link VibratorState#IDLE}. */
+ private static boolean isVibrating(VibratorState state) {
+ return state != VibratorState.IDLE;
+ }
+
/** Wrapper around the static-native methods of {@link VibratorController} for tests. */
@VisibleForTesting
public static class NativeWrapper {
@@ -520,6 +562,9 @@ final class VibratorController {
private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
int braking, long vibrationId);
+ private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
+ long vibrationId);
+
private static native void setExternalControl(long nativePtr, boolean enabled);
private static native void alwaysOnEnable(long nativePtr, long id, long effect,
@@ -586,6 +631,11 @@ final class VibratorController {
return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
}
+ /** Turns vibrator on to perform PWLE effect composed of given points. */
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+ return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
+ }
+
/** Enabled the device vibrator to be controlled by another service. */
public void setExternalControl(boolean enabled) {
setExternalControl(mNativePtr, enabled);
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index e9c38940601c..08da43d8f0e0 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -16,8 +16,12 @@
package com.android.server.vibrator;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.Handler;
+import android.os.Parcel;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.util.Slog;
import android.view.HapticFeedbackConstants;
@@ -58,6 +62,16 @@ public class VibratorFrameworkStatsLogger {
"vibrator.value_vibration_adaptive_haptic_scale",
new Histogram.UniformOptions(20, 0, 2));
+ // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
+ private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_effect_size",
+ new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));
+
+ // Session vibration count in [0, ~840) defined by scaled buckets.
+ private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_session_vibrations",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private final Object mLock = new Object();
private final Handler mHandler;
private final long mVibrationReportedLogIntervalMillis;
@@ -201,4 +215,88 @@ public class VibratorFrameworkStatsLogger {
Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
}
}
+
+ /** Logs when a vendor vibration session successfully started. */
+ public void logVibrationVendorSessionStarted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
+ }
+
+ /**
+ * Logs when a vendor vibration session is interrupted by the platform.
+ *
+ * <p>A vendor session is interrupted if it has successfully started and its end was not
+ * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
+ * the vibrator HAL triggering the session completed callback early.
+ */
+ public void logVibrationVendorSessionInterrupted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
+ }
+
+ /** Logs the number of vibrations requested for a single vendor vibration session. */
+ public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
+ sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
+ }
+
+ /**
+ * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
+ *
+ * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
+ * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
+ */
+ public void logVibrationCountAndSizeIfVendorEffect(int uid,
+ @Nullable CombinedVibration vibration) {
+ if (vibration == null) {
+ return;
+ }
+ boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
+ if (hasVendorEffects) {
+ // Increment CombinedVibration with one or more vendor effects only once.
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
+ }
+ }
+
+ private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
+ if (vibration instanceof CombinedVibration.Mono mono) {
+ if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ return true;
+ }
+ return false;
+ }
+ if (vibration instanceof CombinedVibration.Stereo stereo) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < stereo.getEffects().size(); i++) {
+ if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ hasVendorEffects = true;
+ }
+ }
+ return hasVendorEffects;
+ }
+ if (vibration instanceof CombinedVibration.Sequential sequential) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < sequential.getEffects().size(); i++) {
+ hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
+ sequential.getEffects().get(i));
+ }
+ return hasVendorEffects;
+ }
+ // Unknown combined vibration, skip metrics.
+ return false;
+ }
+
+ private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
+ int dataSize;
+ Parcel vendorData = Parcel.obtain();
+ try {
+ // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
+ // PersistableBundle creates an XML representation for the data in writeToStream, so it
+ // might be larger than the actual data that is transferred between processes.
+ effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+ dataSize = vendorData.dataSize();
+ } finally {
+ vendorData.recycle();
+ }
+ sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index a76d8d6bded0..ae726c15ed79 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.Trace.TRACE_TAG_VIBRATOR;
import static android.os.VibrationAttributes.USAGE_CLASS_ALARM;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -31,6 +32,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
@@ -39,6 +41,7 @@ import android.os.ExternalVibration;
import android.os.ExternalVibrationScale;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.IExternalVibratorService;
import android.os.IVibratorManagerService;
import android.os.IVibratorStateListener;
@@ -56,6 +59,7 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.Flags;
+import android.os.vibrator.IVibrationSessionCallback;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
@@ -94,6 +98,7 @@ import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
/** System implementation of {@link IVibratorManagerService}. */
public class VibratorManagerService extends IVibratorManagerService.Stub {
@@ -155,25 +160,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final SparseArray<VibratorController> mVibrators;
private final VibrationThreadCallbacks mVibrationThreadCallbacks =
new VibrationThreadCallbacks();
+ private final ExternalVibrationCallbacks mExternalVibrationCallbacks =
+ new ExternalVibrationCallbacks();
+ private final VendorVibrationSessionCallbacks mVendorVibrationSessionCallbacks =
+ new VendorVibrationSessionCallbacks();
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
- private VibrationStepConductor mCurrentVibration;
+ private VibrationSession mCurrentSession;
@GuardedBy("mLock")
- private VibrationStepConductor mNextVibration;
- @GuardedBy("mLock")
- private ExternalVibrationSession mCurrentExternalVibration;
+ private VibrationSession mNextSession;
@GuardedBy("mLock")
private boolean mServiceReady;
@VisibleForTesting
final VibrationSettings mVibrationSettings;
+ private final VibrationConfig mVibrationConfig;
private final VibrationScaler mVibrationScaler;
private final VibratorControlService mVibratorControlService;
private final InputDeviceDelegate mInputDeviceDelegate;
private final DeviceAdapter mDeviceAdapter;
@GuardedBy("mLock")
+ @Nullable private SparseArray<VibratorInfo> mVibratorInfos;
+ @GuardedBy("mLock")
@Nullable private VibratorInfo mCombinedVibratorInfo;
@GuardedBy("mLock")
@Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
@@ -183,29 +193,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ // When the system is entering a non-interactive state, we want to cancel
+ // vibrations in case a misbehaving app has abandoned them.
synchronized (mLock) {
- // When the system is entering a non-interactive state, we want to cancel
- // vibrations in case a misbehaving app has abandoned them.
- if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF));
- }
- if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
- }
+ maybeClearCurrentAndNextSessionsLocked(
+ VibratorManagerService.this::shouldCancelOnScreenOffLocked,
+ Status.CANCELLED_BY_SCREEN_OFF);
}
} else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
- if (shouldCancelOnFgUserRequest(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER));
- }
- if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
- }
+ maybeClearCurrentAndNextSessionsLocked(
+ VibratorManagerService.this::shouldCancelOnFgUserRequest,
+ Status.CANCELLED_BY_FOREGROUND_USER);
}
}
}
@@ -220,19 +220,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return;
}
synchronized (mLock) {
- if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS));
- }
- if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
- }
+ maybeClearCurrentAndNextSessionsLocked(
+ VibratorManagerService.this::shouldCancelAppOpModeChangedLocked,
+ Status.CANCELLED_BY_APP_OPS);
}
}
};
- static native long nativeInit(OnSyncedVibrationCompleteListener listener);
+ static native long nativeInit(VibratorManagerNativeCallbacks listener);
static native long nativeGetFinalizer();
@@ -246,6 +241,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
static native void nativeCancelSynced(long nativeServicePtr);
+ static native boolean nativeStartSession(long nativeServicePtr, long sessionId,
+ int[] vibratorIds);
+
+ static native void nativeEndSession(long nativeServicePtr, long sessionId, boolean shouldAbort);
+
+ static native void nativeClearSessions(long nativeServicePtr);
+
@VisibleForTesting
VibratorManagerService(Context context, Injector injector) {
mContext = context;
@@ -253,9 +255,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mHandler = injector.createHandler(Looper.myLooper());
mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
- VibrationConfig vibrationConfig = new VibrationConfig(context.getResources());
- mVibrationSettings = new VibrationSettings(mContext, mHandler, vibrationConfig);
- mVibrationScaler = new VibrationScaler(vibrationConfig, mVibrationSettings);
+ mVibrationConfig = new VibrationConfig(context.getResources());
+ mVibrationSettings = new VibrationSettings(mContext, mHandler, mVibrationConfig);
+ mVibrationScaler = new VibrationScaler(mVibrationConfig, mVibrationSettings);
mVibratorControlService = new VibratorControlService(mContext,
injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
mFrameworkStatsLogger, mLock);
@@ -301,7 +303,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibratorIds = vibratorIds;
mVibrators = new SparseArray<>(mVibratorIds.length);
for (int vibratorId : vibratorIds) {
- mVibrators.put(vibratorId, injector.createVibratorController(vibratorId, listener));
+ VibratorController vibratorController =
+ injector.createVibratorController(vibratorId, listener);
+ mVibrators.put(vibratorId, vibratorController);
}
}
@@ -311,6 +315,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Reset the hardware to a default state, in case this is a runtime restart instead of a
// fresh boot.
mNativeWrapper.cancelSynced();
+ if (Flags.vendorVibrationEffects()) {
+ mNativeWrapper.clearSessions();
+ }
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).reset();
}
@@ -333,13 +340,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@VisibleForTesting
void systemReady() {
Slog.v(TAG, "Initializing VibratorManager service...");
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "systemReady");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "systemReady");
try {
// Will retry to load each vibrator's info, if any request have failed.
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).reloadVibratorInfoIfNeeded();
}
+ synchronized (mLock) {
+ mVibratorInfos = transformAllVibratorsLocked(VibratorController::getVibratorInfo);
+ VibratorInfo[] infos = new VibratorInfo[mVibratorInfos.size()];
+ for (int i = 0; i < mVibratorInfos.size(); i++) {
+ infos[i] = mVibratorInfos.valueAt(i);
+ }
+ mCombinedVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, infos);
+ }
+
mVibrationSettings.onSystemReady();
mInputDeviceDelegate.onSystemReady();
@@ -352,7 +368,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mServiceReady = true;
}
Slog.v(TAG, "VibratorManager service initialized");
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -362,6 +378,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override // Binder call
+ public int getCapabilities() {
+ return (int) mCapabilities;
+ }
+
+ @Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
final VibratorController controller = mVibrators.get(vibratorId);
@@ -413,7 +434,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override // Binder call
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
@Nullable CombinedVibration effect, @Nullable VibrationAttributes attrs) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE_ALWAYS_ON,
@@ -449,20 +470,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
return true;
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override // Binder call
public void vibrate(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
- vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "vibrate");
+ try {
+ vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
}
@Override // Binder call
public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
String reason, int flags, int privFlags) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedback");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedback");
// Note that the `performHapticFeedback` method does not take a token argument from the
// caller, and instead, uses this service as the token. This is to mitigate performance
// impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
@@ -471,7 +497,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
this, flags, privFlags);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -479,13 +505,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
int constant, int inputDeviceId, int inputSource, String reason, int flags,
int privFlags) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
try {
performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant,
inputDeviceId,
inputSource, reason, /* token= */ this, flags, privFlags);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -563,26 +589,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
HalVibration vibrateWithPermissionCheck(int uid, int deviceId, String opPkg,
@NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
String reason, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
- try {
- attrs = fixupVibrationAttributes(attrs, effect);
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.VIBRATE, "vibrate");
- return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
+ attrs = fixupVibrationAttributes(attrs, effect);
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE, "vibrate");
+ return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
}
HalVibration vibrateWithoutPermissionCheck(int uid, int deviceId, String opPkg,
@NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
String reason, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
- try {
- return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
+ return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
}
private HalVibration vibrateInternal(int uid, int deviceId, String opPkg,
@@ -594,20 +610,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
return null;
}
- if (effect.hasVendorEffects()
- && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
- Slog.e(TAG, "vibrate; no permission for vendor effects");
- logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
- return null;
- }
enforceUpdateAppOpsStatsPermission(uid);
if (!isEffectValid(effect)) {
logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
return null;
}
+ if (effect.hasVendorEffects()) {
+ if (!Flags.vendorVibrationEffects()) {
+ Slog.e(TAG, "vibrate; vendor effects feature disabled");
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
+ return null;
+ }
+ if (!hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
+ Slog.e(TAG, "vibrate; no permission for vendor effects");
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
+ return null;
+ }
+ }
// Create Vibration.Stats as close to the received request as possible, for tracking.
- HalVibration vib = new HalVibration(token, effect, callerInfo);
- fillVibrationFallbacks(vib, effect);
+ SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
+ HalVibration vib = session.getVibration();
+ vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
// Force update of user settings before checking if this vibration effect should
@@ -621,27 +644,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
// Check if user settings or DnD is set to ignore this vibration.
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+ CallerInfo ignoredBy = null;
// Check if ongoing vibration is more important than this vibration.
- if (vibrationEndInfo == null) {
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+ if (ignoreStatus == null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session);
+ if (vibrationEndInfo != null) {
+ ignoreStatus = vibrationEndInfo.status;
+ ignoredBy = vibrationEndInfo.endedBy;
+ }
}
// If not ignored so far then try to start this vibration.
- if (vibrationEndInfo == null) {
+ if (ignoreStatus == null) {
+ // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
final long ident = Binder.clearCallingIdentity();
try {
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.notifyEnded();
- vib.stats.reportInterruptedAnotherVibration(
- mCurrentExternalVibration.callerInfo);
- endExternalVibrateLocked(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
- /* continueExternalControl= */ false);
- } else if (mCurrentVibration != null) {
- if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
+ if (mCurrentSession != null) {
+ if (shouldPipelineVibrationLocked(mCurrentSession, vib)) {
// Don't cancel the current vibration if it's pipeline-able.
// Note that if there is a pending next vibration that can't be
// pipelined, it will have already cancelled the current one, so we
@@ -651,22 +672,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
} else {
vib.stats.reportInterruptedAnotherVibration(
- mCurrentVibration.getVibration().callerInfo);
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
+ mCurrentSession.getCallerInfo());
+ mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
/* immediate= */ false);
}
}
- vibrationEndInfo = startVibrationLocked(vib);
+ clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
+ ignoreStatus = startVibrationLocked(session);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// Ignored or failed to start the vibration, end it and report metrics right away.
- if (vibrationEndInfo != null) {
- endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
+ if (ignoreStatus != null) {
+ endSessionLocked(session, ignoreStatus, ignoredBy);
}
return vib;
}
@@ -674,7 +694,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override // Binder call
public void cancelVibrate(int usageFilter, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "cancelVibrate");
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE,
@@ -684,35 +704,193 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
- Vibration.EndInfo cancelledByUserInfo =
- new Vibration.EndInfo(Status.CANCELLED_BY_USER);
+ // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
final long ident = Binder.clearCallingIdentity();
try {
- if (mNextVibration != null
- && shouldCancelVibration(mNextVibration.getVibration(),
- usageFilter, token)) {
- clearNextVibrationLocked(cancelledByUserInfo);
+ // TODO(b/370948466): investigate why token not checked on external vibrations.
+ IBinder cancelToken =
+ (mNextSession instanceof ExternalVibrationSession) ? null : token;
+ if (shouldCancelSession(mNextSession, usageFilter, cancelToken)) {
+ clearNextSessionLocked(Status.CANCELLED_BY_USER);
}
- if (mCurrentVibration != null
- && shouldCancelVibration(mCurrentVibration.getVibration(),
- usageFilter, token)) {
- mCurrentVibration.notifyCancelled(
- cancelledByUserInfo, /* immediate= */false);
+ cancelToken =
+ (mCurrentSession instanceof ExternalVibrationSession) ? null : token;
+ if (shouldCancelSession(mCurrentSession, usageFilter, cancelToken)) {
+ mCurrentSession.requestEnd(Status.CANCELLED_BY_USER);
}
- if (mCurrentExternalVibration != null
- && shouldCancelVibration(
- mCurrentExternalVibration.getCallerInfo().attrs,
- usageFilter)) {
- mCurrentExternalVibration.notifyEnded();
- endExternalVibrateLocked(
- cancelledByUserInfo, /* continueExternalControl= */ false);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @android.annotation.EnforcePermission(allOf = {
+ android.Manifest.permission.VIBRATE,
+ android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+ android.Manifest.permission.START_VIBRATION_SESSIONS,
+ })
+ @Override // Binder call
+ public ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg,
+ int[] vibratorIds, VibrationAttributes attrs, String reason,
+ IVibrationSessionCallback callback) {
+ startVendorVibrationSession_enforcePermission();
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationSession");
+ try {
+ VendorVibrationSession session = startVendorVibrationSessionInternal(
+ uid, deviceId, opPkg, vibratorIds, attrs, reason, callback);
+ return session == null ? null : session.getCancellationSignal();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ VendorVibrationSession startVendorVibrationSessionInternal(int uid, int deviceId, String opPkg,
+ int[] vibratorIds, VibrationAttributes attrs, String reason,
+ IVibrationSessionCallback callback) {
+ if (!Flags.vendorVibrationEffects()) {
+ throw new UnsupportedOperationException("Vibration sessions not supported");
+ }
+ attrs = fixupVibrationAttributes(attrs, /* effect= */ null);
+ CallerInfo callerInfo = new CallerInfo(attrs, uid, deviceId, opPkg, reason);
+ if (callback == null) {
+ Slog.e(TAG, "session callback must not be null");
+ logAndRecordSessionAttempt(callerInfo, Status.IGNORED_ERROR_TOKEN);
+ return null;
+ }
+ if (vibratorIds == null) {
+ vibratorIds = new int[0];
+ }
+ enforceUpdateAppOpsStatsPermission(uid);
+
+ // Create session with adapter that only uses the session vibrators.
+ SparseArray<VibratorController> sessionVibrators = new SparseArray<>(vibratorIds.length);
+ for (int vibratorId : vibratorIds) {
+ VibratorController controller = mVibrators.get(vibratorId);
+ if (controller != null) {
+ sessionVibrators.put(vibratorId, controller);
+ }
+ }
+ DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, sessionVibrators);
+ VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler,
+ mVendorVibrationSessionCallbacks, deviceAdapter, callback);
+
+ if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+ // Force update of user settings before checking if this vibration effect should
+ // be ignored or scaled.
+ mVibrationSettings.update();
+ }
+
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting session " + session.getSessionId());
+ }
+
+ Status ignoreStatus = null;
+ CallerInfo ignoredBy = null;
+
+ // Check if HAL has capability to start sessions.
+ if ((mCapabilities & IVibratorManager.CAP_START_SESSIONS) == 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Missing capability to start sessions, ignoring request");
+ }
+ ignoreStatus = Status.IGNORED_UNSUPPORTED;
+ }
+
+ // Check if vibrator IDs requested are available.
+ if (ignoreStatus == null) {
+ if (vibratorIds.length == 0
+ || vibratorIds.length != deviceAdapter.getAvailableVibratorIds().length) {
+ Slog.e(TAG, "Bad vibrator ids to start session, ignoring request."
+ + " requested=" + Arrays.toString(vibratorIds)
+ + " available=" + Arrays.toString(mVibratorIds));
+ ignoreStatus = Status.IGNORED_UNSUPPORTED;
+ }
+ }
+
+ // Check if user settings or DnD is set to ignore this session.
+ if (ignoreStatus == null) {
+ ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+ }
+
+ // Check if ongoing vibration is more important than this session.
+ if (ignoreStatus == null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session);
+ if (vibrationEndInfo != null) {
+ ignoreStatus = vibrationEndInfo.status;
+ ignoredBy = vibrationEndInfo.endedBy;
+ }
+ }
+
+ if (ignoreStatus == null) {
+ // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ // If not ignored so far then stop ongoing sessions before starting this one.
+ clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
+ if (mCurrentSession != null) {
+ mNextSession = session;
+ mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
+ /* immediate= */ false);
+ } else {
+ ignoreStatus = startVendorSessionLocked(session);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+
+ // Ignored or failed to start the session, end it and report metrics right away.
+ if (ignoreStatus != null) {
+ endSessionLocked(session, ignoreStatus, ignoredBy);
+ }
+ return session;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Status startVendorSessionLocked(VendorVibrationSession session) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked");
+ try {
+ long sessionId = session.getSessionId();
+ if (DEBUG) {
+ Slog.d(TAG, "Starting session " + sessionId + " in HAL");
+ }
+ if (session.isEnded()) {
+ // Session already ended, possibly cancelled by app cancellation signal.
+ return session.getStatus();
+ }
+ int mode = startAppOpModeLocked(session.getCallerInfo());
+ switch (mode) {
+ case AppOpsManager.MODE_ALLOWED:
+ Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
+ // Make sure mCurrentVibration is set while triggering the HAL.
+ mCurrentSession = session;
+ if (!session.linkToDeath()) {
+ mCurrentSession = null;
+ return Status.IGNORED_ERROR_TOKEN;
+ }
+ if (!mNativeWrapper.startSession(sessionId, session.getVibratorIds())) {
+ Slog.e(TAG, "Error starting session " + sessionId + " on vibrators "
+ + Arrays.toString(session.getVibratorIds()));
+ session.unlinkToDeath();
+ mCurrentSession = null;
+ return Status.IGNORED_UNSUPPORTED;
+ }
+ session.notifyStart();
+ return null;
+ case AppOpsManager.MODE_ERRORED:
+ Slog.w(TAG, "Start AppOpsManager operation errored for uid "
+ + session.getCallerInfo().uid);
+ return Status.IGNORED_ERROR_APP_OPS;
+ default:
+ return Status.IGNORED_APP_OPS;
+ }
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -764,8 +942,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println("CurrentVibration:");
pw.increaseIndent();
- if (mCurrentVibration != null) {
- mCurrentVibration.getVibration().getDebugInfo().dump(pw);
+ if (mCurrentSession != null) {
+ mCurrentSession.getDebugInfo().dump(pw);
} else {
pw.println("null");
}
@@ -774,18 +952,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println("NextVibration:");
pw.increaseIndent();
- if (mNextVibration != null) {
- mNextVibration.getVibration().getDebugInfo().dump(pw);
- } else {
- pw.println("null");
- }
- pw.decreaseIndent();
- pw.println();
-
- pw.println("CurrentExternalVibration:");
- pw.increaseIndent();
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dump(pw);
+ if (mNextSession != null) {
+ mNextSession.getDebugInfo().dump(pw);
} else {
pw.println("null");
}
@@ -809,25 +977,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
synchronized (mLock) {
mVibrationSettings.dump(proto);
mVibrationScaler.dump(proto);
- if (mCurrentVibration != null) {
- mCurrentVibration.getVibration().getDebugInfo().dump(proto,
+ if (mCurrentSession != null) {
+ mCurrentSession.getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
}
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dump(proto,
- VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
- }
-
- boolean isVibrating = false;
- boolean isUnderExternalControl = false;
for (int i = 0; i < mVibrators.size(); i++) {
proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
- isVibrating |= mVibrators.valueAt(i).isVibrating();
- isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
}
- proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
- proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
- isUnderExternalControl);
}
mVibratorManagerRecords.dump(proto);
mVibratorControlService.dump(proto);
@@ -854,22 +1010,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
}
- if (mCurrentVibration == null) {
+ // TODO(b/372241975): investigate why external vibrations were not handled here before
+ if (mCurrentSession == null
+ || (mCurrentSession instanceof ExternalVibrationSession)) {
return;
}
- HalVibration vib = mCurrentVibration.getVibration();
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
- if (inputDevicesChanged || (vibrationEndInfo != null)) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentSession.getCallerInfo());
+ if (inputDevicesChanged || (ignoreStatus != null)) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration because settings changed: "
- + (inputDevicesChanged ? "input devices changed"
- : vibrationEndInfo.status));
+ + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
- /* immediate= */ false);
+ mCurrentSession.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
}
}
}
@@ -889,8 +1042,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (vibrator == null) {
continue;
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
- if (vibrationEndInfo == null) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
+ if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -902,68 +1055,107 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
+ private Status startVibrationLocked(SingleVibrationSession session) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
if (mInputDeviceDelegate.isAvailable()) {
- return startVibrationOnInputDevicesLocked(vib);
+ return startVibrationOnInputDevicesLocked(session.getVibration());
}
-
- VibrationStepConductor conductor = createVibrationStepConductor(vib);
-
- if (mCurrentVibration == null) {
- return startVibrationOnThreadLocked(conductor);
+ if (mCurrentSession == null) {
+ return startVibrationOnThreadLocked(session);
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
// Note that we don't consider pipelining here, because new pipelined ones should
// replace pending non-executing pipelined ones anyway.
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
- mNextVibration = conductor;
+ clearNextSessionLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+ mNextSession = session;
return null;
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
- HalVibration vib = conductor.getVibration();
- int mode = startAppOpModeLocked(vib.callerInfo);
+ private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread");
+ }
+ VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
+ session.setVibrationConductor(conductor);
+ int mode = startAppOpModeLocked(session.getCallerInfo());
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the VibrationThread.
- mCurrentVibration = conductor;
- if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
- // Shouldn't happen. The method call already logs a wtf.
- mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
+ mCurrentSession = session;
+ if (!mCurrentSession.linkToDeath()) {
+ // Shouldn't happen. The method call already logs.
+ mCurrentSession = null; // Aborted.
+ return Status.IGNORED_ERROR_TOKEN;
+ }
+ if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
+ // Shouldn't happen. The method call already logs.
+ session.setVibrationConductor(null); // Rejected by thread, clear it in session.
+ mCurrentSession = null; // Aborted.
+ return Status.IGNORED_ERROR_SCHEDULING;
}
return null;
case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid "
- + vib.callerInfo.uid);
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ + session.getCallerInfo().uid);
+ return Status.IGNORED_ERROR_APP_OPS;
default:
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
- boolean shouldWriteStats) {
- vib.end(vibrationEndInfo);
- logAndRecordVibration(vib.getDebugInfo());
- if (shouldWriteStats) {
- mFrameworkStatsLogger.writeVibrationReportedAsync(
- vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
- }
+ private void maybeStartNextSessionLocked() {
+ if (mNextSession instanceof SingleVibrationSession session) {
+ mNextSession = null;
+ Status errorStatus = startVibrationOnThreadLocked(session);
+ if (errorStatus != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Error starting next vibration " + session.getVibration().id);
+ }
+ endSessionLocked(session, errorStatus);
+ }
+ } else if (mNextSession instanceof VendorVibrationSession session) {
+ mNextSession = null;
+ Status errorStatus = startVendorSessionLocked(session);
+ if (errorStatus != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Error starting next session " + session.getSessionId());
+ }
+ endSessionLocked(session, errorStatus);
+ }
+ } // External vibrations cannot be started asynchronously.
+ }
+
+ @GuardedBy("mLock")
+ private void endSessionLocked(VibrationSession session, Status status) {
+ endSessionLocked(session, status, /* endedBy= */ null);
+ }
+
+ @GuardedBy("mLock")
+ private void endSessionLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+ session.requestEnd(status, endedBy, /* immediate= */ false);
+ logAndRecordVibration(session.getDebugInfo());
}
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
+ return createVibrationStepConductor(vib, mDeviceAdapter, /* isInSession= */ false);
+ }
+
+ private VibrationStepConductor createSessionVibrationStepConductor(HalVibration vib,
+ DeviceAdapter deviceAdapter) {
+ return createVibrationStepConductor(vib, deviceAdapter, /* isInSession= */ true);
+ }
+
+ private VibrationStepConductor createVibrationStepConductor(HalVibration vib,
+ DeviceAdapter deviceAdapter, boolean isInSession) {
CompletableFuture<Void> requestVibrationParamsFuture = null;
if (Flags.adaptiveHapticsEnabled()
@@ -975,16 +1167,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibrationSettings.getRequestVibrationParamsTimeoutMs());
}
- return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
- mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
+ return new VibrationStepConductor(vib, isInSession, mVibrationSettings,
+ deviceAdapter, mVibrationScaler, mFrameworkStatsLogger,
+ requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
- private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ private Status startVibrationOnInputDevicesLocked(HalVibration vib) {
// Scale resolves the default amplitudes from the effect before scaling them.
vib.scaleEffects(mVibrationScaler);
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
-
- return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
+ return Status.FORWARDED_TO_INPUT_DEVICES;
}
private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
@@ -997,10 +1189,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
CallerInfo callerInfo, Status status) {
+ logAndRecordVibration(createVibrationAttemptDebugInfo(effect, callerInfo, status));
+ }
+
+ private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) {
logAndRecordVibration(
- new Vibration.DebugInfoImpl(status, new VibrationStats(),
- effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
- VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
+ new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
+ SystemClock.uptimeMillis(), System.currentTimeMillis(),
+ /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
+ /* endedByVendor= */ false, /* vibrations= */ null));
}
private void logAndRecordVibration(DebugInfo info) {
@@ -1009,6 +1206,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibratorManagerRecords.record(info);
}
+ private DebugInfo createVibrationAttemptDebugInfo(@Nullable CombinedVibration effect,
+ CallerInfo callerInfo, Status status) {
+ return new Vibration.DebugInfoImpl(status, callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
+ effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
+ VibrationScaler.ADAPTIVE_SCALE_NONE);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs, Status status) {
switch (status) {
case IGNORED_BACKGROUND:
@@ -1054,41 +1259,40 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- @GuardedBy("mLock")
- private void reportFinishedVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- HalVibration vib = mCurrentVibration.getVibration();
- if (DEBUG) {
- Slog.d(TAG, "Reporting vibration " + vib.id + " finished with "
- + vibrationEndInfo);
+ private void onVibrationSessionComplete(long sessionId) {
+ synchronized (mLock) {
+ if (mCurrentSession == null || mCurrentSession.getSessionId() != sessionId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " callback ignored");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " complete, notifying session");
+ }
+ mCurrentSession.notifySessionCallback();
}
- // DO NOT write metrics at this point, wait for the VibrationThread to report the
- // vibration was released, after all cleanup. The metrics will be reported then.
- endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
- finishAppOpModeLocked(vib.callerInfo);
}
private void onSyncedVibrationComplete(long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentSession != null) {
if (DEBUG) {
Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
}
- mCurrentVibration.notifySyncedVibrationComplete();
+ mCurrentSession.notifySyncedVibratorsCallback(vibrationId);
}
}
}
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentSession != null) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread");
}
- mCurrentVibration.notifyVibratorComplete(vibratorId);
+ mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId);
}
}
}
@@ -1100,14 +1304,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
- if (mCurrentExternalVibration != null) {
- return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration);
- }
-
- if (mNextVibration != null) {
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib,
- mNextVibration.getVibration());
+ private Vibration.EndInfo shouldIgnoreForOngoingLocked(VibrationSession session) {
+ if (mNextSession != null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoing(session,
+ mNextSession);
if (vibrationEndInfo != null) {
// Next vibration has higher importance than the new one, so the new vibration
// should be ignored.
@@ -1115,15 +1315,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- if (mCurrentVibration != null) {
- HalVibration currentVibration = mCurrentVibration.getVibration();
- if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
- // Current vibration has ended or is cancelling, should not block incoming
- // vibrations.
+ if (mCurrentSession != null) {
+ if (mCurrentSession.wasEndRequested()) {
+ // Current session has ended or is cancelling, should not block incoming vibrations.
return null;
}
- return shouldIgnoreVibrationForOngoing(vib, currentVibration);
+ return shouldIgnoreForOngoing(session, mCurrentSession);
}
return null;
@@ -1131,32 +1329,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Checks if the ongoing vibration has higher importance than the new one. If they have similar
- * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker.
+ * importance, then {@link VibrationSession#isRepeating()} is used as a tiebreaker.
*
* @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
*/
@Nullable
- private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
- @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) {
+ private static Vibration.EndInfo shouldIgnoreForOngoing(
+ @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
- int newVibrationImportance = getVibrationImportance(newVibration);
- int ongoingVibrationImportance = getVibrationImportance(ongoingVibration);
+ int newSessionImportance = getVibrationImportance(newSession);
+ int ongoingSessionImportance = getVibrationImportance(ongoingSession);
- if (newVibrationImportance > ongoingVibrationImportance) {
+ if (newSessionImportance > ongoingSessionImportance) {
// New vibration has higher importance and should not be ignored.
return null;
}
- if (ongoingVibrationImportance > newVibrationImportance) {
+ if (ongoingSessionImportance > newSessionImportance) {
// Existing vibration has higher importance and should not be cancelled.
return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
- ongoingVibration.callerInfo);
+ ongoingSession.getCallerInfo());
}
// Same importance, use repeating as a tiebreaker.
- if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
+ if (ongoingSession.isRepeating() && !newSession.isRepeating()) {
// Ongoing vibration is repeating and new one is not, give priority to ongoing
- return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
+ return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING,
+ ongoingSession.getCallerInfo());
}
// New vibration is repeating or this is a complete tie between them,
// give priority to new vibration.
@@ -1170,10 +1369,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* @return a numeric representation for the vibration importance, larger values represent a
* higher importance
*/
- private static int getVibrationImportance(Vibration vibration) {
- int usage = vibration.callerInfo.attrs.getUsage();
+ private static int getVibrationImportance(VibrationSession session) {
+ int usage = session.getCallerInfo().attrs.getUsage();
if (usage == VibrationAttributes.USAGE_UNKNOWN) {
- if (vibration.isRepeating()) {
+ if (session.isRepeating()) {
usage = VibrationAttributes.USAGE_RINGTONE;
} else {
usage = VibrationAttributes.USAGE_TOUCH;
@@ -1200,6 +1399,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
+ /** Returns true if ongoing session should pipeline with the next vibration requested. */
+ @GuardedBy("mLock")
+ private boolean shouldPipelineVibrationLocked(VibrationSession currentSession,
+ HalVibration nextVibration) {
+ if (!(currentSession instanceof SingleVibrationSession currentVibration)) {
+ // Only single vibration session can be pipelined.
+ return false;
+ }
+ return currentVibration.getVibration().canPipelineWith(nextVibration, mVibratorInfos,
+ mVibrationConfig.getVibrationPipelineMaxDurationMs());
+ }
+
/**
* Check if given vibration should be ignored by this service.
*
@@ -1207,10 +1418,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+ private Status shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
if (statusFromSettings != null) {
- return new Vibration.EndInfo(statusFromSettings);
+ return statusFromSettings;
}
int mode = checkAppOpModeLocked(callerInfo);
@@ -1218,9 +1429,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ return Status.IGNORED_ERROR_APP_OPS;
} else {
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@@ -1245,32 +1456,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Return true if the vibration has the same token and usage belongs to given usage class.
*
- * @param vib The ongoing or pending vibration to be cancelled.
+ * @param session The ongoing or pending vibration session to be cancelled.
* @param usageFilter The vibration usages to be cancelled, any bitwise combination of
* VibrationAttributes.USAGE_* values.
- * @param token The binder token to identify the vibration origin. Only vibrations
+ * @param tokenFilter The binder token to identify the vibration origin. Only vibrations
* started with the same token can be cancelled with it.
*/
- private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
- return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs,
- usageFilter);
- }
-
- /**
- * Return true if the external vibration usage belongs to given usage class.
- *
- * @param attrs The attributes of an ongoing or pending vibration to be cancelled.
- * @param usageFilter The vibration usages to be cancelled, any bitwise combination of
- * VibrationAttributes.USAGE_* values.
- */
- private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) {
- if (attrs.getUsage() == VibrationAttributes.USAGE_UNKNOWN) {
+ private boolean shouldCancelSession(@Nullable VibrationSession session, int usageFilter,
+ @Nullable IBinder tokenFilter) {
+ if (session == null) {
+ return false;
+ }
+ if (session instanceof VendorVibrationSession) {
+ // Vendor sessions should not be cancelled by Vibrator.cancel API.
+ return false;
+ }
+ if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
+ // Vibration from a different app, this should not cancel it.
+ return false;
+ }
+ int usage = session.getCallerInfo().attrs.getUsage();
+ if (usage == VibrationAttributes.USAGE_UNKNOWN) {
// Special case, usage UNKNOWN would match all filters. Instead it should only match if
// it's cancelling that usage specifically, or if cancelling all usages.
return usageFilter == VibrationAttributes.USAGE_UNKNOWN
|| usageFilter == VibrationAttributes.USAGE_FILTER_MATCH_ALL;
}
- return (usageFilter & attrs.getUsage()) == attrs.getUsage();
+ return (usageFilter & usage) == usage;
}
/**
@@ -1339,52 +1551,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
try {
effect.validate();
} catch (Exception e) {
- Slog.wtf(TAG, "Encountered issue when verifying CombinedVibrationEffect.", e);
+ Slog.wtf(TAG, "Encountered issue when verifying vibration: " + effect, e);
return false;
}
return true;
}
/**
- * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
- * VibrationSettings#getFallbackEffect}.
- */
- private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
- if (effect instanceof CombinedVibration.Mono) {
- fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
- } else if (effect instanceof CombinedVibration.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibration.Stereo) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.valueAt(i));
- }
- } else if (effect instanceof CombinedVibration.Sequential) {
- List<CombinedVibration> effects =
- ((CombinedVibration.Sequential) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.get(i));
- }
- }
- }
-
- private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
- if (!(effect instanceof VibrationEffect.Composed composed)) {
- return;
- }
- int segmentCount = composed.getSegments().size();
- for (int i = 0; i < segmentCount; i++) {
- VibrationEffectSegment segment = composed.getSegments().get(i);
- if (segment instanceof PrebakedSegment prebaked) {
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
- prebaked.getEffectId());
- if (prebaked.shouldFallback() && fallback != null) {
- vib.addFallback(prebaked.getEffectId(), fallback);
- }
- }
- }
- }
-
- /**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
@@ -1481,30 +1654,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
- private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- HalVibration vib = conductor.getVibration();
- return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo,
- vib.stats.getCreateUptimeMillis());
+ return mVibrationSettings.shouldCancelVibrationOnScreenOff(session.getCallerInfo(),
+ session.getCreateUptimeMillis());
}
@GuardedBy("mLock")
- private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return checkAppOpModeLocked(conductor.getVibration().callerInfo)
- != AppOpsManager.MODE_ALLOWED;
+ return checkAppOpModeLocked(session.getCallerInfo()) != AppOpsManager.MODE_ALLOWED;
}
- @GuardedBy("mLock")
- private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnFgUserRequest(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+ return session.getCallerInfo().attrs.getUsageClass() == USAGE_CLASS_ALARM;
}
@GuardedBy("mLock")
@@ -1575,7 +1745,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override
public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "prepareSyncedVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "prepareSyncedVibration");
try {
if ((mCapabilities & requiredCapabilities) != requiredCapabilities) {
// This sync step requires capabilities this device doesn't have, skipping
@@ -1584,33 +1754,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
return mNativeWrapper.prepareSynced(vibratorIds);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public boolean triggerSyncedVibration(long vibrationId) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "triggerSyncedVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "triggerSyncedVibration");
try {
return mNativeWrapper.triggerSynced(vibrationId);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void cancelSyncedVibration() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelSyncedVibration");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "cancelSyncedVibration");
try {
mNativeWrapper.cancelSynced();
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void noteVibratorOn(int uid, long duration) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "noteVibratorOn");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "noteVibratorOn");
try {
if (duration <= 0) {
// Tried to turn vibrator ON and got:
@@ -1629,97 +1799,261 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} catch (RemoteException e) {
Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void noteVibratorOff(int uid) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "noteVibratorOff");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "noteVibratorOff");
try {
mBatteryStatsService.noteVibratorOff(uid);
mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
} catch (RemoteException e) {
Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
- public void onVibrationCompleted(long vibrationId, Vibration.EndInfo vibrationEndInfo) {
+ public void onVibrationThreadReleased(long vibrationId) {
if (DEBUG) {
- Slog.d(TAG, "Vibration " + vibrationId + " finished with " + vibrationEndInfo);
+ Slog.d(TAG, "VibrationThread released vibration " + vibrationId);
}
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onVibrationCompleted");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased");
try {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
- reportFinishedVibrationLocked(vibrationEndInfo);
+ if (mCurrentSession instanceof SingleVibrationSession session) {
+ if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "VibrationId mismatch on vibration thread release."
+ + " expected=%d, released=%d",
+ session.getVibration().id, vibrationId));
+ }
+ finishAppOpModeLocked(mCurrentSession.getCallerInfo());
+ clearCurrentSessionLocked();
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ // Start next vibration if it's waiting for the thread.
+ maybeStartNextSessionLocked();
+ } else if (mCurrentSession instanceof VendorVibrationSession session) {
+ VibrationStepConductor conductor = session.clearVibrationConductor();
+ if (Build.IS_DEBUGGABLE) {
+ if (conductor == null) {
+ Slog.wtf(TAG, "Vendor session without ongoing vibration on"
+ + " thread release. currentSession=" + mCurrentSession);
+ } else if (conductor.getVibration().id != vibrationId) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "VibrationId mismatch on vibration thread release."
+ + " expected=%d, released=%d",
+ conductor.getVibration().id, vibrationId));
+ }
+ }
+ } else if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
+ + " currentSession=" + mCurrentSession);
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
+ }
+
+ /**
+ * Implementation of {@link ExternalVibrationSession.VibratorManagerHooks} that controls
+ * external vibrations and reports them when finished.
+ */
+ private final class ExternalVibrationCallbacks
+ implements ExternalVibrationSession.VibratorManagerHooks {
@Override
- public void onVibrationThreadReleased(long vibrationId) {
+ public void onExternalVibrationReleased(long vibrationId) {
if (DEBUG) {
- Slog.d(TAG, "VibrationThread released after finished vibration");
+ Slog.d(TAG, "External vibration " + vibrationId + " released");
}
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onVibrationThreadReleased: " + vibrationId);
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationReleased");
try {
synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Processing VibrationThread released callback");
+ if (!(mCurrentSession instanceof ExternalVibrationSession session)) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on external vibration release."
+ + " currentSession=" + mCurrentSession);
+ }
+ // Only external vibration sessions are ended by this callback. Abort.
+ return;
}
- if (Build.IS_DEBUGGABLE && mCurrentVibration != null
- && mCurrentVibration.getVibration().id != vibrationId) {
+ if (Build.IS_DEBUGGABLE && (session.id != vibrationId)) {
Slog.wtf(TAG, TextUtils.formatSimple(
- "VibrationId mismatch on release. expected=%d, released=%d",
- mCurrentVibration.getVibration().id, vibrationId));
+ "VibrationId mismatch on external vibration release."
+ + " expected=%d, released=%d", session.id, vibrationId));
+ }
+ setExternalControl(false, session.stats);
+ clearCurrentSessionLocked();
+ // Start next vibration if it's waiting for the external control to be over.
+ maybeStartNextSessionLocked();
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+ }
+
+ /**
+ * Implementation of {@link ExternalVibrationSession.VibratorManagerHooks} that controls
+ * external vibrations and reports them when finished.
+ */
+ private final class VendorVibrationSessionCallbacks
+ implements VendorVibrationSession.VibratorManagerHooks {
+
+ @Override
+ public void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration effect) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " vibration requested");
+ }
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "sessionVibrate");
+ try {
+ synchronized (mLock) {
+ if (!(mCurrentSession instanceof VendorVibrationSession session)) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on session vibrate."
+ + " currentSession=" + mCurrentSession);
+ }
+ // Only vendor vibration sessions can handle this call. Abort.
+ return;
}
- if (mCurrentVibration != null) {
- // This is when we consider the current vibration complete, so report
- // metrics.
- mFrameworkStatsLogger.writeVibrationReportedAsync(
- mCurrentVibration.getVibration().getStatsInfo(
- /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
- mCurrentVibration = null;
+ if (session.getSessionId() != sessionId) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "SessionId mismatch on vendor vibration session vibrate."
+ + " expected=%d, released=%d",
+ session.getSessionId(), sessionId));
+ }
+ // Only the ongoing vendor vibration sessions can handle this call. Abort.
+ return;
}
- if (mNextVibration != null) {
- VibrationStepConductor nextConductor = mNextVibration;
- mNextVibration = null;
- Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
- nextConductor);
- if (vibrationEndInfo != null) {
- // Failed to start the vibration, end it and report metrics right away.
- endVibrationLocked(nextConductor.getVibration(),
- vibrationEndInfo, /* shouldWriteStats= */ true);
+ if (session.wasEndRequested()) {
+ if (DEBUG) {
+ Slog.d(TAG, "session vibrate; session is ending, vibration ignored");
}
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_ERROR_SCHEDULING));
+ return;
+ }
+ if (!isEffectValid(effect)) {
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_UNSUPPORTED));
+ return;
+ }
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ // Repeating effects cannot be played by the service in a session.
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_UNSUPPORTED));
+ return;
+ }
+ // Create Vibration.Stats as close to the request as possible, for tracking.
+ HalVibration vib = new HalVibration(callerInfo, effect);
+ vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
+
+ if (callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+ // Force update of user settings before checking if this vibration effect
+ // should be ignored or scaled.
+ mVibrationSettings.update();
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Starting vibrate for vibration " + vib.id
+ + " in session " + sessionId);
+ }
+
+ VibrationStepConductor conductor =
+ createSessionVibrationStepConductor(vib, session.getDeviceAdapter());
+ if (session.maybeSetVibrationConductor(conductor)) {
+ if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
+ // Shouldn't happen. The method call already logs.
+ vib.end(new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING));
+ session.clearVibrationConductor(); // Rejected by thread, clear it.
+ }
+ } else {
+ // Cannot set vibration in session, log failed attempt.
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_ERROR_SCHEDULING));
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void endSession(long sessionId, boolean shouldAbort) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId
+ + (shouldAbort ? " aborting" : " ending"));
+ }
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "endSession");
+ try {
+ mNativeWrapper.endSession(sessionId, shouldAbort);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(long sessionId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " released");
+ }
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVendorSessionReleased");
+ try {
+ synchronized (mLock) {
+ if (!(mCurrentSession instanceof VendorVibrationSession session)) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on vibration session release."
+ + " currentSession=" + mCurrentSession);
+ }
+ // Only vendor vibration sessions are ended by this callback. Abort.
+ return;
+ }
+ if (Build.IS_DEBUGGABLE && (session.getSessionId() != sessionId)) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "SessionId mismatch on vendor vibration session release."
+ + " expected=%d, released=%d",
+ session.getSessionId(), sessionId));
+ }
+ // Make sure all controllers in session are reset after session ended.
+ // This will update the vibrator state to isVibrating = false for listeners.
+ for (int vibratorId : session.getVibratorIds()) {
+ mVibrators.get(vibratorId).off();
+ }
+ finishAppOpModeLocked(mCurrentSession.getCallerInfo());
+ clearCurrentSessionLocked();
+ // Start next vibration if it's waiting for the HAL session to be over.
+ maybeStartNextSessionLocked();
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
}
- /** Listener for synced vibration completion callbacks from native. */
+ /** Listener for vibrator manager completion callbacks from native. */
@VisibleForTesting
- interface OnSyncedVibrationCompleteListener {
+ interface VibratorManagerNativeCallbacks {
/** Callback triggered when synced vibration is complete. */
- void onComplete(long vibrationId);
+ void onSyncedVibrationComplete(long vibrationId);
+
+ /** Callback triggered when vibration session is complete. */
+ void onVibrationSessionComplete(long sessionId);
}
/**
* Implementation of listeners to native vibrators with a weak reference to this service.
*/
private static final class VibrationCompleteListener implements
- VibratorController.OnVibrationCompleteListener, OnSyncedVibrationCompleteListener {
+ VibratorController.OnVibrationCompleteListener, VibratorManagerNativeCallbacks {
private WeakReference<VibratorManagerService> mServiceRef;
VibrationCompleteListener(VibratorManagerService service) {
@@ -1727,7 +2061,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
- public void onComplete(long vibrationId) {
+ public void onSyncedVibrationComplete(long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
service.onSyncedVibrationComplete(vibrationId);
@@ -1735,6 +2069,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
+ public void onVibrationSessionComplete(long sessionId) {
+ VibratorManagerService service = mServiceRef.get();
+ if (service != null) {
+ service.onVibrationSessionComplete(sessionId);
+ }
+ }
+
+ @Override
public void onComplete(int vibratorId, long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
@@ -1767,7 +2109,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private long mNativeServicePtr = 0;
/** Returns native pointer to newly created controller and connects with HAL service. */
- public void init(OnSyncedVibrationCompleteListener listener) {
+ public void init(VibratorManagerNativeCallbacks listener) {
mNativeServicePtr = nativeInit(listener);
long finalizerPtr = nativeGetFinalizer();
@@ -1803,6 +2145,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public void cancelSynced() {
nativeCancelSynced(mNativeServicePtr);
}
+
+ /** Start vibration session. */
+ public boolean startSession(long sessionId, @NonNull int[] vibratorIds) {
+ return nativeStartSession(mNativeServicePtr, sessionId, vibratorIds);
+ }
+
+ /** End vibration session. */
+ public void endSession(long sessionId, boolean shouldAbort) {
+ nativeEndSession(mNativeServicePtr, sessionId, shouldAbort);
+ }
+
+ /** Clear vibration sessions. */
+ public void clearSessions() {
+ nativeClearSessions(mNativeServicePtr);
+ }
}
/** Keep records of vibrations played and provide debug information for this service. */
@@ -1922,40 +2279,74 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
- if (mNextVibration != null) {
+ private void clearNextSessionLocked(Status status) {
+ clearNextSessionLocked(status, /* endedBy= */ null);
+ }
+
+ /** Clears mNextVibration if set, ending it cleanly */
+ @GuardedBy("mLock")
+ private void clearNextSessionLocked(Status status, CallerInfo endedBy) {
+ if (mNextSession != null) {
if (DEBUG) {
- Slog.d(TAG, "Dropping pending vibration " + mNextVibration.getVibration().id
- + " with end info: " + vibrationEndInfo);
+ Slog.d(TAG, "Dropping pending vibration from " + mNextSession.getCallerInfo()
+ + " with status: " + status);
}
// Clearing next vibration before playing it, end it and report metrics right away.
- endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo,
- /* shouldWriteStats= */ true);
- mNextVibration = null;
+ endSessionLocked(mNextSession, status, endedBy);
+ mNextSession = null;
+ }
+ }
+
+ /** Clears mCurrentVibration if set, reporting metrics */
+ @GuardedBy("mLock")
+ private void clearCurrentSessionLocked() {
+ if (mCurrentSession != null) {
+ mCurrentSession.unlinkToDeath();
+ logAndRecordVibration(mCurrentSession.getDebugInfo());
+ mCurrentSession = null;
+ mLock.notify(); // Notify if waiting for current vibration to end.
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void maybeClearCurrentAndNextSessionsLocked(
+ Predicate<VibrationSession> shouldEndSessionPredicate, Status endStatus) {
+ // TODO(b/372241975): investigate why external vibrations were not handled here before
+ if (!(mNextSession instanceof ExternalVibrationSession)
+ && shouldEndSessionPredicate.test(mNextSession)) {
+ clearNextSessionLocked(endStatus);
+ }
+ if (!(mCurrentSession instanceof ExternalVibrationSession)
+ && shouldEndSessionPredicate.test(mCurrentSession)) {
+ mCurrentSession.requestEnd(endStatus);
}
}
/**
- * Ends the external vibration, and clears related service state.
+ * Waits until the current vibration finished processing, timing out after the given
+ * number of milliseconds.
*
- * @param vibrationEndInfo the status and related info to end the associated Vibration
- * @param continueExternalControl indicates whether external control will continue. If not, the
- * HAL will have external control turned off.
+ * @return true if the vibration completed, or false if waiting timed out.
*/
- @GuardedBy("mLock")
- private void endExternalVibrateLocked(Vibration.EndInfo vibrationEndInfo,
- boolean continueExternalControl) {
- if (mCurrentExternalVibration == null) {
- return;
- }
- mCurrentExternalVibration.unlinkToDeath();
- if (!continueExternalControl) {
- setExternalControl(false, mCurrentExternalVibration.stats);
+ public boolean waitForCurrentSessionEnd(long maxWaitMillis) {
+ long now = SystemClock.elapsedRealtime();
+ long deadline = now + maxWaitMillis;
+ synchronized (mLock) {
+ while (true) {
+ if (mCurrentSession == null) {
+ return true; // Done
+ }
+ if (now >= deadline) { // Note that thread.wait(0) waits indefinitely.
+ return false; // Timed out.
+ }
+ try {
+ mLock.wait(deadline - now);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "VibratorManagerService interrupted waiting to stop, continuing");
+ }
+ now = SystemClock.elapsedRealtime();
+ }
}
- // The external control was turned off, end it and report metrics right away.
- endVibrationLocked(mCurrentExternalVibration, vibrationEndInfo,
- /* shouldWriteStats= */ true);
- mCurrentExternalVibration = null;
}
private HapticFeedbackVibrationProvider getHapticVibrationProvider() {
@@ -1974,33 +2365,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
+ @Nullable
private VibratorInfo getCombinedVibratorInfo() {
synchronized (mLock) {
- // Used a cached resolving vibrator if one exists.
- if (mCombinedVibratorInfo != null) {
- return mCombinedVibratorInfo;
- }
-
- // Return an empty resolving vibrator if the service has no vibrator.
- if (mVibratorIds.length == 0) {
- return mCombinedVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
- }
-
- // Combine the vibrator infos of all the service's vibrator to create a single resolving
- // vibrator that is based on the combined info.
- VibratorInfo[] infos = new VibratorInfo[mVibratorIds.length];
- for (int i = 0; i < mVibratorIds.length; i++) {
- VibratorInfo info = getVibratorInfo(mVibratorIds[i]);
- // If any one of the service's vibrator does not have a valid vibrator info, stop
- // trying to create and cache a combined resolving vibrator. Combine the infos only
- // when infos for all vibrators are available.
- if (info == null) {
- return null;
- }
- infos[i] = info;
- }
-
- return mCombinedVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, infos);
+ // This is only initialized at system ready, when all vibrator infos are fully loaded.
+ return mCombinedVibratorInfo;
}
}
@@ -2010,22 +2379,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override
public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onExternalVibrationStart");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStart");
try {
// Create Vibration.Stats as close to the received request as possible, for
// tracking.
- ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
+ ExternalVibrationSession session = new ExternalVibrationSession(vib,
+ mExternalVibrationCallbacks);
// Mute the request until we run all the checks and accept the vibration.
- externalVibration.muteScale();
- boolean alreadyUnderExternalControl = false;
+ session.muteScale();
boolean waitForCompletion = false;
synchronized (mLock) {
if (!hasExternalControlCapability()) {
- endVibrationLocked(externalVibration,
- new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED),
- /* shouldWriteStats= */ true);
- return externalVibration.getScale();
+ endSessionLocked(session, Status.IGNORED_UNSUPPORTED);
+ return session.getScale();
}
if (ActivityManager.checkComponentPermission(
@@ -2035,135 +2402,120 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- endVibrationLocked(externalVibration,
- new Vibration.EndInfo(Status.IGNORED_MISSING_PERMISSION),
- /* shouldWriteStats= */ true);
- return externalVibration.getScale();
+ endSessionLocked(session, Status.IGNORED_MISSING_PERMISSION);
+ return session.getScale();
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
- externalVibration.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
+ if (ignoreStatus != null) {
+ endSessionLocked(session, ignoreStatus);
+ return session.getScale();
+ }
- if (vibrationEndInfo == null
- && mCurrentExternalVibration != null
- && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
+ if ((mCurrentSession instanceof ExternalVibrationSession evs)
+ && evs.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
- return mCurrentExternalVibration.getScale();
+ return evs.getScale();
}
- if (vibrationEndInfo == null) {
- // Check if ongoing vibration is more important than this vibration.
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
+ // Check if ongoing vibration is more important than this vibration.
+ Vibration.EndInfo ignoreInfo = shouldIgnoreForOngoingLocked(session);
+ if (ignoreInfo != null) {
+ endSessionLocked(session, ignoreInfo.status, ignoreInfo.endedBy);
+ return session.getScale();
}
- if (vibrationEndInfo != null) {
- endVibrationLocked(externalVibration, vibrationEndInfo,
- /* shouldWriteStats= */ true);
- return externalVibration.getScale();
- }
+ // First clear next request, so it won't start when the current one ends.
+ clearNextSessionLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo);
+ mNextSession = session;
- if (mCurrentExternalVibration == null) {
- // If we're not under external control right now, then cancel any normal
- // vibration that may be playing and ready the vibrator for external
- // control.
- if (mCurrentVibration != null) {
- externalVibration.stats.reportInterruptedAnotherVibration(
- mCurrentVibration.getVibration().callerInfo);
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
- externalVibration.callerInfo));
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo),
- /* immediate= */ true);
- waitForCompletion = true;
- }
- } else {
- // At this point we have an externally controlled vibration playing already.
+ if (mCurrentSession != null) {
+ // Cancel any vibration that may be playing and ready the vibrator, even if
+ // we have an externally controlled vibration playing already.
// Since the interface defines that only one externally controlled
- // vibration can
- // play at a time, we need to first mute the ongoing vibration and then
- // return
- // a scale from this function for the new one, so we can be assured that the
- // ongoing will be muted in favor of the new vibration.
+ // vibration can play at a time, we need to first mute the ongoing vibration
+ // and then return a scale from this function for the new one, so we can be
+ // assured that the ongoing will be muted in favor of the new vibration.
//
// Note that this doesn't support multiple concurrent external controls,
// as we would need to mute the old one still if it came from a different
// controller.
- alreadyUnderExternalControl = true;
- mCurrentExternalVibration.notifyEnded();
- externalVibration.stats.reportInterruptedAnotherVibration(
- mCurrentExternalVibration.callerInfo);
- endExternalVibrateLocked(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo),
- /* continueExternalControl= */ true);
+ session.stats.reportInterruptedAnotherVibration(
+ mCurrentSession.getCallerInfo());
+ mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED,
+ session.callerInfo, /* immediate= */ true);
+ waitForCompletion = true;
}
-
- VibrationAttributes attrs = fixupVibrationAttributes(
- vib.getVibrationAttributes(),
- /* effect= */ null);
- if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
- // Force update of user settings before checking if this vibration effect
- // should be ignored or scaled.
- mVibrationSettings.update();
- }
-
- mCurrentExternalVibration = externalVibration;
- externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
- externalVibration.scale(mVibrationScaler, attrs.getUsage());
}
-
+ // Wait for lock and interact with HAL to set external control outside main lock.
if (waitForCompletion) {
- if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
+ if (!waitForCurrentSessionEnd(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- // Trigger endExternalVibrateLocked to unlink to death recipient.
- endExternalVibrateLocked(
- new Vibration.EndInfo(Status.IGNORED_ERROR_CANCELLING),
- /* continueExternalControl= */ false);
- // Mute the request, vibration will be ignored.
- externalVibration.muteScale();
+ if (mNextSession == session) {
+ mNextSession = null;
+ }
+ endSessionLocked(session, Status.IGNORED_ERROR_CANCELLING);
+ return session.getScale();
}
- return externalVibration.getScale();
}
}
- if (!alreadyUnderExternalControl) {
+ synchronized (mLock) {
+ if (mNextSession == session) {
+ // This is still the next vibration to be played.
+ mNextSession = null;
+ } else {
+ // A new request took the place of this one, maybe with higher importance.
+ // Next vibration already cleared with the right status, just return here.
+ return session.getScale();
+ }
+ if (!session.linkToDeath()) {
+ endSessionLocked(session, Status.IGNORED_ERROR_TOKEN);
+ return session.getScale();
+ }
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true, externalVibration.stats);
- }
- if (DEBUG) {
- Slog.d(TAG, "Playing external vibration: " + vib);
+ setExternalControl(true, session.stats);
+ if (DEBUG) {
+ Slog.d(TAG, "Playing external vibration: " + vib);
+ }
+ VibrationAttributes attrs = fixupVibrationAttributes(
+ vib.getVibrationAttributes(), /* effect= */ null);
+ if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+ // Force update of user settings before checking if this vibration effect
+ // should be ignored or scaled.
+ mVibrationSettings.update();
+ }
+ mCurrentSession = session;
+ session.scale(mVibrationScaler, attrs.getUsage());
+
+ // Vibrator will start receiving data from external channels after this point.
+ // Report current time as the vibration start time, for debugging.
+ session.stats.reportStarted();
+ return session.getScale();
}
- // Vibrator will start receiving data from external channels after this point.
- // Report current time as the vibration start time, for debugging.
- externalVibration.stats.reportStarted();
- return externalVibration.getScale();
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@Override
public void onExternalVibrationStop(ExternalVibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
try {
synchronized (mLock) {
- if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
+ if ((mCurrentSession instanceof ExternalVibrationSession evs)
+ && evs.isHoldingSameVibration(vib)) {
if (DEBUG) {
Slog.d(TAG, "Stopping external vibration: " + vib);
}
- endExternalVibrateLocked(
- new Vibration.EndInfo(Status.FINISHED),
- /* continueExternalControl= */ false);
+ mCurrentSession.requestEnd(Status.FINISHED);
}
}
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
@@ -2175,19 +2527,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
return false;
}
-
- private void onExternalVibrationBinderDied() {
- synchronized (mLock) {
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "External vibration finished because binder died");
- }
- endExternalVibrateLocked(
- new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
- /* continueExternalControl= */ false);
- }
- }
- }
}
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
@@ -2232,32 +2571,39 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override
public int onCommand(String cmd) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd);
try {
if ("list".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: list");
return runListVibrators();
}
if ("synced".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: synced");
return runMono();
}
if ("combined".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: combined");
return runStereo();
}
if ("sequential".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: sequential");
return runSequential();
}
if ("xml".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: xml");
return runXml();
}
if ("cancel".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: cancel");
return runCancel();
}
if ("feedback".equals(cmd)) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: feedback");
return runHapticFeedback();
}
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onCommand: default");
return handleDefaultCommands(cmd);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index d5bea4adaf8c..b3e68a35764b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -19,6 +19,7 @@ package com.android.server.wallpaper;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
+import static android.app.Flags.accurateWallpaperDownsampling;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
@@ -378,7 +379,14 @@ public class WallpaperCropper {
for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
Rect adjustedRect = new Rect(wallpaper.mCropHints.valueAt(i));
adjustedRect.offset(-wallpaper.cropHint.left, -wallpaper.cropHint.top);
- adjustedRect.scale(1f / wallpaper.mSampleSize);
+ if (accurateWallpaperDownsampling()) {
+ adjustedRect.left = (int) (0.5f + adjustedRect.left / wallpaper.mSampleSize);
+ adjustedRect.top = (int) (0.5f + adjustedRect.top / wallpaper.mSampleSize);
+ adjustedRect.right = (int) Math.floor(adjustedRect.right / wallpaper.mSampleSize);
+ adjustedRect.bottom = (int) Math.floor(adjustedRect.bottom / wallpaper.mSampleSize);
+ } else {
+ adjustedRect.scale(1f / wallpaper.mSampleSize);
+ }
result.put(wallpaper.mCropHints.keyAt(i), adjustedRect);
}
return result;
@@ -603,6 +611,11 @@ public class WallpaperCropper {
float sampleSizeForThisOrientation = Math.max(1f, Math.min(
crop.width() / displayForThisOrientation.x,
crop.height() / displayForThisOrientation.y));
+ if (accurateWallpaperDownsampling()) {
+ sampleSizeForThisOrientation = Math.max(1f, Math.min(
+ (float) crop.width() / displayForThisOrientation.x,
+ (float) crop.height() / displayForThisOrientation.y));
+ }
sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
}
// If the total crop has more width or height than either the max texture size
@@ -746,8 +759,8 @@ public class WallpaperCropper {
final ImageDecoder.Source srcData =
ImageDecoder.createSource(wallpaper.getWallpaperFile());
final int finalScale = scale;
- final int rescaledBitmapWidth = (int) (0.5f + bitmapSize.x / sampleSize);
- final int rescaledBitmapHeight = (int) (0.5f + bitmapSize.y / sampleSize);
+ final int rescaledBitmapWidth = (int) Math.ceil(bitmapSize.x / sampleSize);
+ final int rescaledBitmapHeight = (int) Math.ceil(bitmapSize.y / sampleSize);
Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
if (!multiCrop()) decoder.setTargetSampleSize(finalScale);
if (multiCrop()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 15f86e9c08ff..f09b0a1c6e41 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.app.Flags.liveWallpaperContentHandling;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
@@ -25,10 +26,12 @@ import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_CROP;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+import android.annotation.NonNull;
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperColors;
import android.app.WallpaperManager.ScreenOrientation;
import android.app.WallpaperManager.SetWallpaperFlags;
+import android.app.wallpaper.WallpaperDescription;
import android.content.ComponentName;
import android.graphics.Rect;
import android.os.RemoteCallbackList;
@@ -77,6 +80,8 @@ class WallpaperData {
/**
* The component name of the currently set live wallpaper.
+ *
+ * @deprecated
*/
private ComponentName mWallpaperComponent;
@@ -146,6 +151,7 @@ class WallpaperData {
UNKNOWN,
CONNECT_LOCKED,
CONNECTION_TRY_TO_REBIND,
+ FALLBACK_DEFAULT_MISSING,
INITIALIZE_FALLBACK,
PACKAGE_UPDATE_FINISHED,
RESTORE_SETTINGS_LIVE_FAILURE,
@@ -179,6 +185,9 @@ class WallpaperData {
*/
int mOrientationWhenSet = ORIENTATION_UNKNOWN;
+ /** Description of the current wallpaper */
+ private WallpaperDescription mDescription = new WallpaperDescription.Builder().build();
+
WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
this.userId = userId;
this.mWhich = wallpaperType;
@@ -206,6 +215,9 @@ class WallpaperData {
this.primaryColors = source.primaryColors;
this.mWallpaperDimAmount = source.mWallpaperDimAmount;
this.connection = source.connection;
+ if (liveWallpaperContentHandling()) {
+ this.setDescription(source.getDescription());
+ }
if (this.connection != null) {
this.connection.mWallpaper = this;
}
@@ -230,14 +242,40 @@ class WallpaperData {
return result;
}
- ComponentName getComponent() {
- return mWallpaperComponent;
+ @NonNull ComponentName getComponent() {
+ if (liveWallpaperContentHandling()) {
+ return mDescription.getComponent();
+ } else {
+ return mWallpaperComponent;
+ }
}
- void setComponent(ComponentName componentName) {
+ void setComponent(@NonNull ComponentName componentName) {
+ if (liveWallpaperContentHandling()) {
+ throw new IllegalStateException(
+ "Use \"setDescription\" when content handling is enabled");
+ }
this.mWallpaperComponent = componentName;
}
+ @NonNull WallpaperDescription getDescription() {
+ return mDescription;
+ }
+
+ void setDescription(@NonNull WallpaperDescription description) {
+ if (!liveWallpaperContentHandling()) {
+ throw new IllegalStateException(
+ "Use \"setContent\" when content handling is disabled");
+ }
+ if (description == null) {
+ throw new IllegalArgumentException("WallpaperDescription must not be null");
+ }
+ if (description.getComponent() == null) {
+ throw new IllegalArgumentException("WallpaperDescription component must not be null");
+ }
+ this.mDescription = description;
+ }
+
@Override
public String toString() {
StringBuilder out = new StringBuilder(defaultString(this));
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 74ca23038666..ba0262a8bd19 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.app.Flags.liveWallpaperContentHandling;
import static android.app.Flags.removeNextWallpaperComponent;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -30,11 +31,13 @@ import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
import static com.android.window.flags.Flags.multiCrop;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.backup.WallpaperBackupHelper;
+import android.app.wallpaper.WallpaperDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -177,15 +180,8 @@ public class WallpaperDataParser {
success = true;
} catch (FileNotFoundException e) {
Slog.w(TAG, "no current wallpaper -- first boot?");
- } catch (NullPointerException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (IOException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
- } catch (IndexOutOfBoundsException e) {
+ } catch (NullPointerException | NumberFormatException | XmlPullParserException
+ | IOException | IndexOutOfBoundsException e) {
Slog.w(TAG, "failed parsing " + file + " " + e);
}
IoUtils.closeQuietly(stream);
@@ -194,9 +190,12 @@ public class WallpaperDataParser {
if (loadSystem) {
if (!success) {
+ // Set safe values that won't cause crashes
wallpaper.cropHint.set(0, 0, 0, 0);
wpdData.mPadding.set(0, 0, 0, 0);
wallpaper.name = "";
+ // TODO (b/379936272) Find a safe value for wallpaper component. mImageComponent
+ // does not work at least on some platforms.
} else {
if (wallpaper.wallpaperId <= 0) {
wallpaper.wallpaperId = makeWallpaperIdLocked();
@@ -245,27 +244,14 @@ public class WallpaperDataParser {
parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
}
- String comp = parser.getAttributeValue(null, "component");
- if (removeNextWallpaperComponent()) {
- wallpaperToParse.setComponent(comp != null
- ? ComponentName.unflattenFromString(comp)
- : null);
- if (wallpaperToParse.getComponent() == null
- || "android".equals(wallpaperToParse.getComponent()
- .getPackageName())) {
- wallpaperToParse.setComponent(mImageWallpaper);
- }
- } else {
- wallpaperToParse.nextWallpaperComponent = comp != null
- ? ComponentName.unflattenFromString(comp)
- : null;
- if (wallpaperToParse.nextWallpaperComponent == null
- || "android".equals(wallpaperToParse.nextWallpaperComponent
- .getPackageName())) {
- wallpaperToParse.nextWallpaperComponent = mImageWallpaper;
+ ComponentName comp = parseComponentName(parser);
+ if (!liveWallpaperContentHandling()) {
+ if (removeNextWallpaperComponent()) {
+ wallpaperToParse.setComponent(comp);
+ } else {
+ wallpaperToParse.nextWallpaperComponent = comp;
}
}
-
if (multiCrop()) {
parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
}
@@ -290,6 +276,17 @@ public class WallpaperDataParser {
return lockWallpaper;
}
+ @NonNull
+ private ComponentName parseComponentName(TypedXmlPullParser parser) {
+ String comp = parser.getAttributeValue(null, "component");
+ ComponentName c = (comp != null) ? ComponentName.unflattenFromString(comp) : null;
+ if (c == null || "android".equals(c.getPackageName())) {
+ c = mImageWallpaper;
+ }
+
+ return c;
+ }
+
private void ensureSaneWallpaperData(WallpaperData wallpaper) {
// Only overwrite cropHint if the rectangle is invalid.
if (wallpaper.cropHint.width() < 0
@@ -332,9 +329,29 @@ public class WallpaperDataParser {
}
}
+ void parseWallpaperDescription(TypedXmlPullParser parser, WallpaperData wallpaper)
+ throws XmlPullParserException, IOException {
+
+ int type = parser.next();
+ if (type == XmlPullParser.START_TAG && "description".equals(parser.getName())) {
+ // Always read the description if it's there - there may be one from a previous save
+ // with content handling enabled even if it's enabled now
+ WallpaperDescription description = WallpaperDescription.restoreFromXml(parser);
+ if (liveWallpaperContentHandling()) {
+ // null component means that wallpaper was last saved without content handling, so
+ // populate description from saved component
+ if (description.getComponent() == null) {
+ description = description.toBuilder().setComponent(
+ parseComponentName(parser)).build();
+ }
+ wallpaper.setDescription(description);
+ }
+ }
+ }
+
@VisibleForTesting
void parseWallpaperAttributes(TypedXmlPullParser parser, WallpaperData wallpaper,
- boolean keepDimensionHints) throws XmlPullParserException {
+ boolean keepDimensionHints) throws XmlPullParserException, IOException {
final int id = parser.getAttributeInt(null, "id", -1);
if (id != -1) {
wallpaper.wallpaperId = id;
@@ -355,8 +372,7 @@ public class WallpaperDataParser {
getAttributeInt(parser, "totalCropTop", 0),
getAttributeInt(parser, "totalCropRight", 0),
getAttributeInt(parser, "totalCropBottom", 0));
- ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent()
- : wallpaper.nextWallpaperComponent;
+ ComponentName componentName = parseComponentName(parser);
if (multiCrop() && mImageWallpaper.equals(componentName)) {
wallpaper.mCropHints = new SparseArray<>();
for (Pair<Integer, String> pair: screenDimensionPairs()) {
@@ -443,6 +459,15 @@ public class WallpaperDataParser {
}
wallpaper.name = parser.getAttributeValue(null, "name");
wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
+
+ parseWallpaperDescription(parser, wallpaper);
+ if (liveWallpaperContentHandling() && wallpaper.getDescription().getComponent() == null) {
+ // The last save was done before the content handling flag was enabled and has no
+ // WallpaperDescription, so create a default one with the correct component.
+ // CSP: log boot after flag change to false -> true
+ wallpaper.setDescription(
+ new WallpaperDescription.Builder().setComponent(componentName).build());
+ }
}
private static int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
@@ -610,9 +635,27 @@ public class WallpaperDataParser {
out.attributeBoolean(null, "backup", true);
}
+ writeWallpaperDescription(out, wallpaper);
+
out.endTag(null, tag);
}
+ void writeWallpaperDescription(TypedXmlSerializer out, WallpaperData wallpaper)
+ throws IOException {
+ if (liveWallpaperContentHandling()) {
+ WallpaperDescription description = wallpaper.getDescription();
+ if (description != null) {
+ String descriptionTag = "description";
+ out.startTag(null, descriptionTag);
+ try {
+ description.saveToXml(out);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Error writing wallpaper description", e);
+ }
+ out.endTag(null, descriptionTag);
+ }
+ }
+ }
// Restore the named resource bitmap to both source + crop files
boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
@@ -692,9 +735,9 @@ public class WallpaperDataParser {
private static List<Pair<Integer, String>> screenDimensionPairs() {
return List.of(
- new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
- new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
- new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
- new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
+ new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+ new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+ new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+ new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, "SquareLandscape"));
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4754ffb5cf6e..415896b6230f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -20,6 +20,8 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.Flags.fixWallpaperChanged;
+import static android.app.Flags.liveWallpaperContentHandling;
import static android.app.Flags.removeNextWallpaperComponent;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
@@ -48,6 +50,7 @@ import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.offloadColorExtraction;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
@@ -65,6 +68,8 @@ import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -349,7 +354,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.d(TAG, "publish system wallpaper changed!");
}
- notifyWallpaperChanged(wallpaper);
+ notifyWallpaperComplete(wallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(wallpaper);
+ }
}
};
@@ -369,7 +377,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.d(TAG, "publish lock wallpaper changed!");
}
- notifyWallpaperChanged(wallpaper);
+ notifyWallpaperComplete(wallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(wallpaper);
+ }
}
};
@@ -403,8 +414,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private void notifyWallpaperChanged(WallpaperData wallpaper) {
- // Publish completion *after* we've persisted the changes
+ /*
+ * Calls wallpaper setComplete methods. Called for static wallpapers after the wallpaper is set
+ * and changes are persisted.
+ */
+ private void notifyWallpaperComplete(WallpaperData wallpaper) {
if (wallpaper.setComplete != null) {
try {
wallpaper.setComplete.onWallpaperChanged();
@@ -527,7 +541,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* @return true unless the wallpaper changed during the color computation
*/
private boolean extractColors(WallpaperData wallpaper) {
- if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent());
+ if (offloadColorExtraction()) return true;
String cropFile = null;
boolean defaultImageWallpaper = false;
int wallpaperId;
@@ -839,16 +853,26 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
try {
- connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo);
+ if (liveWallpaperContentHandling()) {
+ connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+ wpdData.mWidth, wpdData.mHeight,
+ wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
+ wallpaper.getDescription());
+ } else {
+ WallpaperDescription desc = new WallpaperDescription.Builder().setComponent(
+ (connection.mInfo != null) ? connection.mInfo.getComponent()
+ : null).build();
+ connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+ wpdData.mWidth, wpdData.mHeight,
+ wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, desc);
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper on display", e);
if (wallpaper != null && !wallpaper.wallpaperUpdating
&& connection.getConnectedEngineSize() == 0) {
wallpaper.mBindSource = BindSource.CONNECT_LOCKED;
- bindWallpaperComponentLocked(null /* componentName */, false /* force */,
- false /* fromUser */, wallpaper, null /* reply */);
+ bindWallpaperComponentLocked(null, false /* force */, false /* fromUser */,
+ wallpaper, null /* reply */);
}
}
t.traceEnd();
@@ -1314,7 +1338,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.v(TAG, "static system+lock to system success");
}
- lockWp.setComponent(mOriginalSystem.getComponent());
+ if (liveWallpaperContentHandling()) {
+ lockWp.setDescription(mOriginalSystem.getDescription());
+ } else {
+ lockWp.setComponent(mOriginalSystem.getComponent());
+ }
lockWp.connection = mOriginalSystem.connection;
lockWp.connection.mWallpaper = lockWp;
mOriginalSystem.mWhich = FLAG_LOCK;
@@ -1381,7 +1409,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.i(TAG, "Wallpaper " + wpService + " update has finished");
}
wallpaper.wallpaperUpdating = false;
- clearWallpaperComponentLocked(wallpaper);
+ detachWallpaperLocked(wallpaper);
wallpaper.mBindSource = BindSource.PACKAGE_UPDATE_FINISHED;
if (!bindWallpaperComponentLocked(wpService, false, false,
wallpaper, null)) {
@@ -1468,7 +1496,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|| change == PACKAGE_TEMPORARY_CHANGE) {
changed = true;
if (doit) {
- Slog.w(TAG, "Wallpaper uninstalled, removing: "
+ Slog.e(TAG, "Wallpaper uninstalled, removing: "
+ wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
@@ -1486,12 +1514,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
if (wallpaper.getComponent() != null
&& isPackageModified(wallpaper.getComponent().getPackageName())) {
+ ServiceInfo serviceInfo = null;
try {
- mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- } catch (NameNotFoundException e) {
- Slog.w(TAG, "Wallpaper component gone, removing: "
+ serviceInfo = mIPackageManager.getServiceInfo(
+ wallpaper.getComponent(), PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call IPackageManager.getServiceInfo", e);
+ }
+ if (serviceInfo == null) {
+ Slog.e(TAG, "Wallpaper component gone, removing: "
+ wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
@@ -1787,6 +1819,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
switchWallpaper(systemWallpaper, null);
// TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
notifyCallbacksLocked(systemWallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(systemWallpaper);
+ }
}
if (mLockWallpaperWaitingForUnlock) {
final WallpaperData lockWallpaper =
@@ -1794,6 +1829,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
lockWallpaper.mBindSource = BindSource.SWITCH_WALLPAPER_UNLOCK_USER;
switchWallpaper(lockWallpaper, null);
notifyCallbacksLocked(lockWallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(lockWallpaper);
+ }
}
// Make sure that the SELinux labeling of all the relevant files is correct.
@@ -1889,6 +1927,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
+ if (liveWallpaperContentHandling()) {
+ final WallpaperDescription description = wallpaper.getDescription();
+ if (!bindWallpaperDescriptionLocked(description, true, false, wallpaper, reply)) {
+ // We failed to bind the desired wallpaper, but that might
+ // happen if the wallpaper isn't direct-boot aware
+ ServiceInfo si = null;
+ try {
+ si = mIPackageManager.getServiceInfo(description.getComponent(),
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
+ }
+ onSwitchWallpaperFailLocked(wallpaper, reply, si);
+ }
+ return;
+ }
+
final ComponentName cname;
if (removeNextWallpaperComponent()) {
cname = wallpaper.getComponent();
@@ -1985,9 +2040,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
if (userId != mCurrentUserId && !hasCrossUserPermission()) return;
- final ComponentName component;
- final int finalWhich;
-
// Clear any previous ImageWallpaper related fields
List<WallpaperData> toClear = new ArrayList<>();
if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
@@ -2001,19 +2053,34 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- // lock only case: set the system wallpaper component to both screens
- if (which == FLAG_LOCK) {
- component = wallpaper.getComponent();
- finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+ final WallpaperDescription description;
+ final int finalWhich;
+
+ if (liveWallpaperContentHandling()) {
+ if (which == FLAG_LOCK) {
+ // lock only case: set the system wallpaper component to both screens
+ description = wallpaper.getDescription();
+ finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+ } else {
+ description = new WallpaperDescription.Builder().build();
+ finalWhich = which;
+ }
} else {
- component = null;
- finalWhich = which;
+ if (which == FLAG_LOCK) {
+ // lock only case: set the system wallpaper component to both screens
+ description = new WallpaperDescription.Builder().setComponent(
+ wallpaper.getComponent()).build();
+ finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+ } else {
+ description = new WallpaperDescription.Builder().build();
+ finalWhich = which;
+ }
}
// except for the lock case (for which we keep the system wallpaper as-is), force rebind
boolean force = which != FLAG_LOCK;
- boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal(
- component, finalWhich, userId, force, fromForeground, reply));
+ boolean success = withCleanCallingIdentity(() -> setWallpaperDescriptionInternal(
+ description, finalWhich, userId, force, fromForeground, reply));
if (success) return;
} catch (IllegalArgumentException e1) {
e = e1;
@@ -2024,7 +2091,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// let's not let it crash the system and just live with no
// wallpaper.
Slog.e(TAG, "Default wallpaper component not found!", e);
- withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
+ withCleanCallingIdentity(() -> {
+ wallpaper.mBindSource = BindSource.FALLBACK_DEFAULT_MISSING;
+ bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, reply);
+ });
if (reply != null) {
try {
reply.sendResult(null);
@@ -2315,8 +2385,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
SparseArray<Rect> relativeSuggestedCrops =
mWallpaperCropper.getRelativeCropHints(wallpaper);
Point croppedBitmapSize = new Point(
- (int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize),
- (int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize));
+ (int) Math.ceil(wallpaper.cropHint.width() / wallpaper.mSampleSize),
+ (int) Math.ceil(wallpaper.cropHint.height() / wallpaper.mSampleSize));
if (croppedBitmapSize.equals(0, 0)) {
// There is an ImageWallpaper, but there are no crop hints and the bitmap size is
// unknown (e.g. the default wallpaper). Return a special "null" value that will be
@@ -2345,6 +2415,27 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
+ public Bundle getCurrentBitmapCrops(int which, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getBitmapCrop", null);
+ synchronized (mLock) {
+ checkPermission(READ_WALLPAPER_INTERNAL);
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) {
+ return null;
+ }
+ Bundle bundle = new Bundle();
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ String key = String.valueOf(wallpaper.mCropHints.keyAt(i));
+ Rect rect = wallpaper.mCropHints.valueAt(i);
+ bundle.putParcelable(key, rect);
+ }
+ return bundle;
+ }
+ }
+
+ @Override
public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
int[] screenOrientations, List<Rect> crops) {
SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
@@ -2396,6 +2487,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
+ if (liveWallpaperContentHandling()) {
+ WallpaperInstance instance = getWallpaperInstance(which, userId, false);
+ return (instance != null) ? instance.getInfo() : null;
+ }
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
@@ -2409,13 +2504,44 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperInfo info = wallpaper.connection.mInfo;
if (hasPermission(READ_WALLPAPER_INTERNAL)
|| mPackageManagerInternal.canQueryPackage(
- Binder.getCallingUid(), info.getComponent().getPackageName())) {
+ Binder.getCallingUid(), info.getComponent().getPackageName())) {
return info;
}
}
return null;
}
+ @Nullable
+ @Override
+ public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId) {
+ return getWallpaperInstance(which, userId, true);
+ }
+
+ private WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId,
+ boolean requireReadWallpaper) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
+ synchronized (mLock) {
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null || wallpaper.connection == null) return null;
+
+ WallpaperInfo info = wallpaper.connection.mInfo;
+ boolean canQueryPackage = (info == null) || mPackageManagerInternal.canQueryPackage(
+ Binder.getCallingUid(), info.getComponent().getPackageName());
+ if (hasPermission(READ_WALLPAPER_INTERNAL)
+ || (canQueryPackage && !requireReadWallpaper)) {
+ // TODO(b/380245309) Remove this when crops are part of the description.
+ WallpaperDescription description =
+ wallpaper.getDescription().toBuilder().setCropHints(
+ wallpaper.mCropHints).build();
+ return new WallpaperInstance(info, description);
+ } else {
+ return null;
+ }
+ }
+ }
+
@Override
public ParcelFileDescriptor getWallpaperInfoFile(int userId) {
synchronized (mLock) {
@@ -3056,7 +3182,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
}
int orientation = screenOrientations[i];
- if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+ if (orientation == ORIENTATION_UNKNOWN && crops.size() > 1) {
throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
+ "screen orientation should only be used in a singleton map");
}
@@ -3143,11 +3269,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
- public void setWallpaperComponentChecked(ComponentName name, String callingPackage,
- @SetWallpaperFlags int which, int userId) {
+ public void setWallpaperComponentChecked(WallpaperDescription description,
+ String callingPackage, @SetWallpaperFlags int which, int userId) {
if (isWallpaperSupported(callingPackage) && isSetWallpaperAllowed(callingPackage)) {
- setWallpaperComponent(name, callingPackage, which, userId);
+ setWallpaperDescription(description, callingPackage, which, userId);
}
}
@@ -3160,12 +3286,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@VisibleForTesting
boolean setWallpaperComponent(ComponentName name, String callingPackage,
@SetWallpaperFlags int which, int userId) {
+ return setWallpaperDescription(
+ new WallpaperDescription.Builder().setComponent(name).build(), callingPackage,
+ which, userId);
+ }
+
+ @VisibleForTesting
+ boolean setWallpaperDescription(WallpaperDescription description, String callingPackage,
+ @SetWallpaperFlags int which, int userId) {
boolean fromForeground = isFromForegroundApp(callingPackage);
- return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
+ return setWallpaperDescriptionInternal(description, which, userId, false, fromForeground,
+ null);
}
- private boolean setWallpaperComponentInternal(ComponentName name, @SetWallpaperFlags int which,
- int userIdIn, boolean force, boolean fromForeground, IRemoteCallback reply) {
+ private boolean setWallpaperDescriptionInternal(@NonNull WallpaperDescription description,
+ @SetWallpaperFlags int which, int userIdIn, boolean force, boolean fromForeground,
+ IRemoteCallback reply) {
+ ComponentName name = description.getComponent();
if (DEBUG) {
Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
}
@@ -3175,11 +3312,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
boolean shouldNotifyColors = false;
+ final boolean bindSuccess;
// If the lockscreen wallpaper is set to the same as the home screen, notify that the
// lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
boolean shouldNotifyLockscreenColors = false;
- boolean bindSuccess;
final WallpaperData newWallpaper;
synchronized (mLock) {
@@ -3210,8 +3347,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final WallpaperDestinationChangeHandler
liveSync = new WallpaperDestinationChangeHandler(
newWallpaper);
- boolean same = changingToSame(name, newWallpaper.connection,
- newWallpaper.getComponent());
+ boolean same;
+ if (liveWallpaperContentHandling()) {
+ same = changingToSame(description, newWallpaper.connection,
+ newWallpaper.getDescription());
+ } else {
+ same = changingToSame(name, newWallpaper.connection,
+ newWallpaper.getComponent());
+ }
/*
* If we have a shared system+lock wallpaper, and we reapply the same wallpaper
@@ -3222,8 +3365,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
newWallpaper.mBindSource =
(name == null) ? BindSource.SET_LIVE_TO_CLEAR : BindSource.SET_LIVE;
- bindSuccess = bindWallpaperComponentLocked(name, /* force */
- forceRebind, /* fromUser */ true, newWallpaper, reply);
+ if (liveWallpaperContentHandling()) {
+ bindSuccess = bindWallpaperDescriptionLocked(description, forceRebind,
+ /* fromUser */ true, newWallpaper, reply);
+ } else {
+ bindSuccess = bindWallpaperComponentLocked(name, forceRebind,
+ /* fromUser */ true, newWallpaper, reply);
+ }
if (bindSuccess) {
if (!same || (offloadColorExtraction() && forceRebind)) {
newWallpaper.primaryColors = null;
@@ -3248,6 +3396,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
newWallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(newWallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(newWallpaper);
+ }
shouldNotifyColors = true;
if (offloadColorExtraction()) {
shouldNotifyColors = false;
@@ -3316,14 +3467,64 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return false;
}
+ private boolean changingToSame(WallpaperDescription newDescription,
+ WallpaperConnection currentConnection, WallpaperDescription currentDescription) {
+ if (currentConnection == null) {
+ return false;
+ }
+ if (isDefaultComponent(newDescription.getComponent()) && isDefaultComponent(
+ currentDescription.getComponent())) {
+ if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+ // Still using default wallpaper.
+ return true;
+ } else if (newDescription.equals(currentDescription)) {
+ // Changing to same wallpaper.
+ if (DEBUG) Slog.v(TAG, "same wallpaper");
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * Attempt to bind the wallpaper given by `componentName`, returning true on success otherwise
+ * false.
+ *
+ * When called, `wallpaper` is in a deliberately inconsistent state. Most fields have been
+ * updated to describe the desired wallpaper, but the ComponentName is not updated until
+ * binding is successful. This is required for maybeDetachWallpapers() to work correctly.
+ *
+ * The late update of the component field should cause multi-threading headaches with
+ * WallpaperConnection#onServiceConnected, but doesn't because onServiceConnected required
+ * `mLock` and `bindWallpaperComponentLocked` is always called with that lock, which prevents a
+ * race condition.
+ *
+ * This is a major motivation for making WallpaperData immutable per b/267170056.
+ */
boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+ return bindWallpaperDescriptionLocked(
+ new WallpaperDescription.Builder().setComponent(componentName).build(), force,
+ fromUser, wallpaper, reply);
+ }
+
+ // When `liveWallpaperContentHandling()` is false this acts exactly like the version which takes
+ // a ComponentName argument did: it uses the ComponentName from `description`, it binds the
+ // service given by that component, and updates WallpaperData with that component on success.
+ boolean bindWallpaperDescriptionLocked(WallpaperDescription description, boolean force,
+ boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+ ComponentName componentName = description.getComponent();
if (DEBUG_LIVE) {
Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
}
- // Has the component changed?
- if (!force && changingToSame(componentName, wallpaper.connection,
- wallpaper.getComponent())) {
+ boolean skipBinding;
+ if (liveWallpaperContentHandling()) {
+ skipBinding = !force && changingToSame(description, wallpaper.connection,
+ wallpaper.getDescription());
+ } else {
+ skipBinding = !force && changingToSame(componentName, wallpaper.connection,
+ wallpaper.getComponent());
+ }
+ if (skipBinding) {
try {
if (DEBUG_LIVE) {
Slog.v(TAG, "Changing to the same component, ignoring");
@@ -3340,6 +3541,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
if (componentName == null) {
componentName = mDefaultWallpaperComponent;
+ if (liveWallpaperContentHandling()) {
+ description = description.toBuilder().setComponent(componentName).build();
+ }
}
int serviceUserId = wallpaper.userId;
ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
@@ -3460,7 +3664,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return false;
}
maybeDetachLastWallpapers(wallpaper);
- wallpaper.setComponent(componentName);
+ if (liveWallpaperContentHandling()) {
+ wallpaper.setDescription(description);
+ } else {
+ wallpaper.setComponent(componentName);
+ }
wallpaper.connection = newConn;
newConn.mReply = reply;
updateCurrentWallpapers(wallpaper);
@@ -3584,11 +3792,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
});
}
- private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
- wallpaper.setComponent(null);
- detachWallpaperLocked(wallpaper);
- }
-
private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("WPMS.attachServiceLocked");
@@ -3610,8 +3813,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
wallpaper.callbacks.finishBroadcast();
+ if (!fixWallpaperChanged()) {
+ final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+ intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP,
+ wallpaper.fromForegroundApp);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
+ }
+ }
+
+ private void notifyWallpaperChanged(WallpaperData wallpaper) {
final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, wallpaper.fromForegroundApp);
+ intent.putExtra(WallpaperManager.EXTRA_WHICH_WALLPAPER_CHANGED, wallpaper.mWhich);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
@@ -3814,6 +4027,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
// Called by SystemBackupAgent after files are restored to disk.
+ // TODO(b/373875373) Remove this method
public void settingsRestored() {
// Verify caller is the system
if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
@@ -3832,6 +4046,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
ComponentName componentName =
removeNextWallpaperComponent() ? wallpaper.getComponent()
: wallpaper.nextWallpaperComponent;
+
+ if (liveWallpaperContentHandling()) {
+ // Per b/373875373 this method should be removed, so we just set wallpapers to
+ // default.
+ bindWallpaperDescriptionLocked(new WallpaperDescription.Builder().build(), false,
+ false, wallpaper, null);
+ return;
+ }
if (componentName != null && !componentName.equals(mImageWallpaper)) {
wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS;
if (!bindWallpaperComponentLocked(componentName, false, false,
@@ -3850,15 +4072,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
success = true;
} else {
- if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+ if (DEBUG) {
+ Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+ }
success = mWallpaperDataParser.restoreNamedResourceLocked(wallpaper);
}
- if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
- + " id=" + wallpaper.wallpaperId);
+ if (DEBUG) {
+ Slog.v(TAG, "settingsRestored: success=" + success + " id="
+ + wallpaper.wallpaperId);
+ }
if (success) {
- mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata
+ mWallpaperCropper.generateCrop(
+ wallpaper); // based on the new image + metadata
wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_STATIC;
- bindWallpaperComponentLocked(componentName, true, false, wallpaper, null);
+ bindWallpaperComponentLocked(null, true, false, wallpaper, null);
}
}
}
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index 6776f268e743..e697d1509b1e 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -19,7 +19,6 @@ package com.android.server.wearable;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
-import android.app.wearable.Flags;
import android.app.wearable.IWearableSensingCallback;
import android.app.wearable.WearableSensingManager;
import android.content.ComponentName;
@@ -42,7 +41,7 @@ import java.io.IOException;
final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
private static final String TAG =
com.android.server.wearable.RemoteWearableSensingService.class.getSimpleName();
- private final static boolean DEBUG = false;
+ private static final boolean DEBUG = false;
private final Object mSecureConnectionLock = new Object();
@@ -55,11 +54,12 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
@GuardedBy("mSecureConnectionLock")
private boolean mSecureConnectionProvided = false;
- RemoteWearableSensingService(Context context, ComponentName serviceName,
- int userId) {
- super(context, new Intent(
- WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName),
- BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ RemoteWearableSensingService(Context context, ComponentName serviceName, int userId) {
+ super(
+ context,
+ new Intent(WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES,
+ userId,
IWearableSensingService.Stub::asInterface);
// Bind right away
@@ -87,15 +87,6 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
if (DEBUG) {
Slog.i(TAG, "#provideSecureConnection");
}
- if (!Flags.enableRestartWssProcess()) {
- Slog.d(
- TAG,
- "FLAG_ENABLE_RESTART_WSS_PROCESS is disabled. Do not attempt to restart the"
- + " WearableSensingService process");
- provideSecureConnectionInternal(
- secureWearableConnection, wearableSensingCallback, statusCallback);
- return;
- }
synchronized (mSecureConnectionLock) {
if (mNextSecureConnectionContext != null) {
// A process restart is in progress, #binderDied is about to be called. Replace
@@ -103,13 +94,11 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
Slog.i(
TAG,
"A new wearable connection is provided before the process restart triggered"
- + " by the previous connection is complete. Discarding the previous"
- + " connection.");
- if (Flags.enableProvideWearableConnectionApi()) {
- WearableSensingManagerPerUserService.notifyStatusCallback(
- mNextSecureConnectionContext.mStatusCallback,
- WearableSensingManager.STATUS_CHANNEL_ERROR);
- }
+ + " by the previous connection is complete. Discarding the previous"
+ + " connection.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ mNextSecureConnectionContext.mStatusCallback,
+ WearableSensingManager.STATUS_CHANNEL_ERROR);
mNextSecureConnectionContext =
new SecureWearableConnectionContext(
secureWearableConnection, wearableSensingCallback, statusCallback);
@@ -130,6 +119,32 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
}
}
+ public void provideConcurrentSecureConnection(
+ ParcelFileDescriptor secureWearableConnection,
+ PersistableBundle metadata,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
+ if (DEBUG) {
+ Slog.i(TAG, "#provideConcurrentSecureConnection");
+ }
+ var unused =
+ post(
+ service -> {
+ service.provideConcurrentSecureConnection(
+ secureWearableConnection,
+ metadata,
+ wearableSensingCallback,
+ statusCallback);
+ try {
+ // close the local fd after it has been sent to the
+ // WearableSensingService process
+ secureWearableConnection.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
+ }
+
private void provideSecureConnectionInternal(
ParcelFileDescriptor secureWearableConnection,
IWearableSensingCallback wearableSensingCallback,
@@ -174,6 +189,28 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
var unused = post(service -> service.killProcess());
}
+ /** Provides a read-only {@link ParcelFileDescriptor} to the WearableSensingService. */
+ public void provideReadOnlyParcelFileDescriptor(
+ ParcelFileDescriptor parcelFileDescriptor,
+ PersistableBundle metadata,
+ RemoteCallback callback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Providing read-only ParcelFileDescriptor.");
+ }
+ var unused =
+ post(
+ service -> {
+ service.provideReadOnlyParcelFileDescriptor(
+ parcelFileDescriptor, metadata, callback);
+ try {
+ // close the local fd after it has been sent to the WSS process
+ parcelFileDescriptor.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
+ }
+
/**
* Provides the implementation a data stream to the wearable.
*
@@ -210,9 +247,8 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
* @param sharedMemory The unrestricted data blob to provide to the implementation.
* @param callback The callback for service status
*/
- public void provideData(PersistableBundle data,
- SharedMemory sharedMemory,
- RemoteCallback callback) {
+ public void provideData(
+ PersistableBundle data, SharedMemory sharedMemory, RemoteCallback callback) {
if (DEBUG) {
Slog.i(TAG, "Providing data.");
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index 36e52008f223..395816902592 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -51,6 +51,7 @@ import android.system.OsConstants;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
@@ -59,21 +60,22 @@ import com.android.server.infra.AbstractPerUserSystemService;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
-/**
- * Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables.
- */
-final class WearableSensingManagerPerUserService extends
- AbstractPerUserSystemService<WearableSensingManagerPerUserService,
- WearableSensingManagerService> {
+/** Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables. */
+final class WearableSensingManagerPerUserService
+ extends AbstractPerUserSystemService<
+ WearableSensingManagerPerUserService, WearableSensingManagerService> {
private static final String TAG = WearableSensingManagerPerUserService.class.getSimpleName();
private final PackageManagerInternal mPackageManagerInternal;
- @Nullable
- @VisibleForTesting
- RemoteWearableSensingService mRemoteService;
+ @Nullable @VisibleForTesting RemoteWearableSensingService mRemoteService;
@Nullable private VoiceInteractionManagerInternal mVoiceInteractionManagerInternal;
@@ -85,16 +87,32 @@ final class WearableSensingManagerPerUserService extends
@GuardedBy("mSecureChannelLock")
private WearableSensingSecureChannel mSecureChannel;
+ // mSecureChannelMap is used by the WearableSensingManager#provideConnection(
+ // WearableConnection, Executor) API, which allows up to mMaxNumberOfConcurrentConnections
+ // concurrent connections, while the mSecureChannel above is used by the deprecated
+ // #provideConnection(ParcelFileDescriptor, Executor, Consumer) API, which does not allow
+ // concurrent connections.
+ @GuardedBy("mSecureChannelMap")
+ private final Map<Integer, WearableSensingSecureChannel> mSecureChannelMap = new HashMap<>();
+
+ private final AtomicInteger mNextConnectionId = new AtomicInteger(1);
+
+ private final int mMaxNumberOfConcurrentConnections;
+
WearableSensingManagerPerUserService(
@NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
super(master, lock, userId);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mMaxNumberOfConcurrentConnections =
+ getContext()
+ .getResources()
+ .getInteger(
+ R.integer.config_maxWearableSensingServiceConcurrentConnections);
}
public static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) {
Bundle bundle = new Bundle();
- bundle.putInt(
- WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode);
+ bundle.putInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode);
statusCallback.sendResult(bundle);
}
@@ -116,8 +134,8 @@ final class WearableSensingManagerPerUserService extends
@GuardedBy("mLock")
private void ensureRemoteServiceInitiated() {
if (mRemoteService == null) {
- mRemoteService = new RemoteWearableSensingService(
- getContext(), mComponentName, getUserId());
+ mRemoteService =
+ new RemoteWearableSensingService(getContext(), mComponentName, getUserId());
}
}
@@ -130,18 +148,15 @@ final class WearableSensingManagerPerUserService extends
return mVoiceInteractionManagerInternal != null;
}
- /**
- * get the currently bound component name.
- */
+ /** get the currently bound component name. */
@VisibleForTesting
ComponentName getComponentName() {
return mComponentName;
}
-
/**
- * Resolves and sets up the service if it had not been done yet. Returns true if the service
- * is available.
+ * Resolves and sets up the service if it had not been done yet. Returns true if the service is
+ * available.
*/
@GuardedBy("mLock")
@VisibleForTesting
@@ -155,8 +170,7 @@ final class WearableSensingManagerPerUserService extends
ServiceInfo serviceInfo;
try {
- serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
- mComponentName, 0, mUserId);
+ serviceInfo = AppGlobals.getPackageManager().getServiceInfo(mComponentName, 0, mUserId);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while setting up service");
return false;
@@ -169,17 +183,17 @@ final class WearableSensingManagerPerUserService extends
throws PackageManager.NameNotFoundException {
ServiceInfo serviceInfo;
try {
- serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
- 0, mUserId);
+ serviceInfo =
+ AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, mUserId);
if (serviceInfo != null) {
final String permission = serviceInfo.permission;
- if (!Manifest.permission.BIND_WEARABLE_SENSING_SERVICE.equals(
- permission)) {
- throw new SecurityException(String.format(
- "Service %s requires %s permission. Found %s permission",
- serviceInfo.getComponentName(),
- Manifest.permission.BIND_WEARABLE_SENSING_SERVICE,
- serviceInfo.permission));
+ if (!Manifest.permission.BIND_WEARABLE_SENSING_SERVICE.equals(permission)) {
+ throw new SecurityException(
+ String.format(
+ "Service %s requires %s permission. Found %s permission",
+ serviceInfo.getComponentName(),
+ Manifest.permission.BIND_WEARABLE_SENSING_SERVICE,
+ serviceInfo.permission));
}
}
} catch (RemoteException e) {
@@ -199,6 +213,20 @@ final class WearableSensingManagerPerUserService extends
}
}
+ /** Returns the number of available concurrent connection quota. */
+ public int getAvailableConnectionCount() {
+ synchronized (mSecureChannelMap) {
+ if (mSecureChannelMap.size() > mMaxNumberOfConcurrentConnections) {
+ Slog.e(
+ TAG,
+ "mMaxNumberOfConcurrentConnections exceeded. This should not be"
+ + " possible!");
+ return 0;
+ }
+ return mMaxNumberOfConcurrentConnections - mSecureChannelMap.size();
+ }
+ }
+
/**
* Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
* service.
@@ -245,36 +273,198 @@ final class WearableSensingManagerPerUserService extends
@Override
public void onError() {
- if (Flags.enableRestartWssProcess()) {
- synchronized (mSecureChannelLock) {
- if (mSecureChannel != null
- && mSecureChannel
- == currentSecureChannelRef.get()) {
- mRemoteService
- .killWearableSensingServiceProcess();
- mSecureChannel = null;
- }
+ synchronized (mSecureChannelLock) {
+ if (mSecureChannel != null
+ && mSecureChannel
+ == currentSecureChannelRef.get()) {
+ mRemoteService.killWearableSensingServiceProcess();
+ mSecureChannel = null;
}
}
- if (Flags.enableProvideWearableConnectionApi()) {
- notifyStatusCallback(
- statusCallback,
- WearableSensingManager.STATUS_CHANNEL_ERROR);
- }
+ notifyStatusCallback(
+ statusCallback,
+ WearableSensingManager.STATUS_CHANNEL_ERROR);
}
});
currentSecureChannelRef.set(mSecureChannel);
} catch (IOException ex) {
Slog.e(TAG, "Unable to create the secure channel.", ex);
- if (Flags.enableProvideWearableConnectionApi()) {
- notifyStatusCallback(
- statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR);
- }
+ notifyStatusCallback(statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR);
}
}
}
/**
+ * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
+ * service.
+ */
+ public int onProvideConcurrentConnection(
+ ParcelFileDescriptor wearableConnection,
+ PersistableBundle metadata,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
+ Slog.i(TAG, "onProvideConcurrentConnection in per user service.");
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return WearableSensingManager.CONNECTION_ID_INVALID;
+ }
+ }
+ boolean isConcurrentConnectionLimitReached = false;
+ synchronized (mSecureChannelMap) {
+ // Do a pre-check on concurrent connection count. We need another check right before we
+ // add the connection into the map to prevent race conditions, but that can only happen
+ // after the WearableSensingSecureChannel is created. This check here allows us to
+ // reject before creating a new secure channel.
+ if (mSecureChannelMap.size() >= mMaxNumberOfConcurrentConnections) {
+ isConcurrentConnectionLimitReached = true;
+ }
+ }
+ if (isConcurrentConnectionLimitReached) {
+ Slog.i(
+ TAG,
+ "Rejecting connection because max concurrent connections limit has been"
+ + " reached.");
+ if (Flags.enableConcurrentWearableConnections()) {
+ notifyStatusCallback(
+ statusCallback,
+ WearableSensingManager.STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED);
+ }
+ return WearableSensingManager.CONNECTION_ID_INVALID;
+ }
+ int connectionId = mNextConnectionId.getAndIncrement();
+ RemoteCallback wrappedStatusCallback =
+ wrapCallbackWithSecureChannelMapCleanUp(statusCallback, connectionId);
+ WearableSensingSecureChannel secureChannel;
+ try {
+ secureChannel =
+ WearableSensingSecureChannel.create(
+ getContext().getSystemService(CompanionDeviceManager.class),
+ wearableConnection,
+ new WearableSensingSecureChannel.SecureTransportListener() {
+ @Override
+ public void onSecureTransportAvailable(
+ ParcelFileDescriptor secureTransport) {
+ Slog.i(TAG, "calling over to remote service.");
+ synchronized (mLock) {
+ ensureRemoteServiceInitiated();
+ mRemoteService.provideConcurrentSecureConnection(
+ secureTransport,
+ metadata,
+ wearableSensingCallback,
+ wrappedStatusCallback);
+ }
+ }
+
+ @Override
+ public void onError() {
+ synchronized (mSecureChannelMap) {
+ mSecureChannelMap.remove(connectionId);
+ }
+ notifyStatusCallback(
+ wrappedStatusCallback,
+ WearableSensingManager.STATUS_CHANNEL_ERROR);
+ }
+ });
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to create the secure channel.", ex);
+ notifyStatusCallback(statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR);
+ return WearableSensingManager.CONNECTION_ID_INVALID;
+ }
+ synchronized (mSecureChannelMap) {
+ if (mSecureChannelMap.size() >= mMaxNumberOfConcurrentConnections) {
+ isConcurrentConnectionLimitReached = true;
+ } else {
+ mSecureChannelMap.put(connectionId, secureChannel);
+ }
+ }
+ if (isConcurrentConnectionLimitReached) {
+ Slog.i(
+ TAG,
+ "Rejecting connection because max concurrent connections limit has been"
+ + " reached.");
+ if (Flags.enableConcurrentWearableConnections()) {
+ notifyStatusCallback(
+ statusCallback,
+ WearableSensingManager.STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED);
+ }
+ secureChannel.close();
+ return WearableSensingManager.CONNECTION_ID_INVALID;
+ }
+ return connectionId;
+ }
+
+ private RemoteCallback wrapCallbackWithSecureChannelMapCleanUp(
+ RemoteCallback statusCallback, int connectionId) {
+ return new RemoteCallback(
+ result -> {
+ int status = result.getInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY);
+ if (status != WearableSensingManager.STATUS_SUCCESS) {
+ removeConnection(connectionId);
+ }
+ statusCallback.sendResult(result);
+ });
+ }
+
+ /**
+ * Removes a connection by ID.
+ *
+ * @param connectionId The ID of the connection to remove.
+ * @return true if the {@code connectionId} corresponds to a stored connection. Returns false
+ * otherwise.
+ */
+ public boolean removeConnection(int connectionId) {
+ WearableSensingSecureChannel removedChannel;
+ synchronized (mSecureChannelMap) {
+ removedChannel = mSecureChannelMap.remove(connectionId);
+ }
+ if (removedChannel != null) {
+ removedChannel.close();
+ return true;
+ }
+ return false;
+ }
+
+ /** Removes all stored connections. */
+ public void removeAllConnections() {
+ List<WearableSensingSecureChannel> allChannels;
+ synchronized (mSecureChannelMap) {
+ allChannels = new ArrayList<>(mSecureChannelMap.values());
+ mSecureChannelMap.clear();
+ }
+ for (WearableSensingSecureChannel channel : allChannels) {
+ channel.close();
+ }
+ }
+
+ /**
+ * Handles sending the provided read-only {@link ParcelFileDescriptor} to the wearable sensing
+ * service.
+ */
+ public void onProvideReadOnlyParcelFileDescriptor(
+ ParcelFileDescriptor parcelFileDescriptor,
+ PersistableBundle metadata,
+ RemoteCallback statusCallback) {
+ if (!isReadOnly(parcelFileDescriptor)) {
+ throw new IllegalArgumentException("Provided ParcelFileDescriptor is not read-only.");
+ }
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ Slog.i(TAG, "calling over to remote servvice.");
+ ensureRemoteServiceInitiated();
+ mRemoteService.provideReadOnlyParcelFileDescriptor(
+ parcelFileDescriptor, metadata, statusCallback);
+ }
+ }
+
+ /**
* Handles sending the provided data stream for the wearable to the wearable sensing service.
*/
public void onProvideDataStream(
@@ -301,12 +491,9 @@ final class WearableSensingManagerPerUserService extends
}
}
- /**
- * Handles sending the provided data to the wearable sensing service.
- */
- public void onProvidedData(PersistableBundle data,
- SharedMemory sharedMemory,
- RemoteCallback callback) {
+ /** Handles sending the provided data to the wearable sensing service. */
+ public void onProvidedData(
+ PersistableBundle data, SharedMemory sharedMemory, RemoteCallback callback) {
synchronized (mLock) {
if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment.");
@@ -557,7 +744,7 @@ final class WearableSensingManagerPerUserService extends
Slog.w(
TAG,
"Error encountered when trying to determine if the parcelFileDescriptor is"
- + " read-only. Treating it as not read-only",
+ + " read-only. Treating it as not read-only",
ex);
}
return false;
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index ac419a541ccd..7297a23c1707 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -66,11 +66,11 @@ import java.util.function.Consumer;
* System service for managing sensing {@link AmbientContextEvent}s on Wearables.
*
* <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated
- * separately. </p>
+ * separately.
*/
-public class WearableSensingManagerService extends
- AbstractMasterSystemService<WearableSensingManagerService,
- WearableSensingManagerPerUserService> {
+public class WearableSensingManagerService
+ extends AbstractMasterSystemService<
+ WearableSensingManagerService, WearableSensingManagerPerUserService> {
private static final String TAG = WearableSensingManagerService.class.getSimpleName();
private static final String KEY_SERVICE_ENABLED = "service_enabled";
@@ -111,11 +111,11 @@ public class WearableSensingManagerService extends
volatile boolean mIsServiceEnabled;
public WearableSensingManagerService(Context context) {
- super(context,
+ super(
+ context,
new FrameworkResourcesServiceNameResolver(
- context,
- R.string.config_defaultWearableSensingService),
- /*disallowProperty=*/null,
+ context, R.string.config_defaultWearableSensingService),
+ /* disallowProperty= */ null,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
@@ -141,24 +141,27 @@ public class WearableSensingManagerService extends
getContext().getMainExecutor(),
(properties) -> onDeviceConfigChange(properties.getKeyset()));
- mIsServiceEnabled = DeviceConfig.getBoolean(
- NAMESPACE_WEARABLE_SENSING,
- KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ mIsServiceEnabled =
+ DeviceConfig.getBoolean(
+ NAMESPACE_WEARABLE_SENSING,
+ KEY_SERVICE_ENABLED,
+ DEFAULT_SERVICE_ENABLED);
}
}
-
private void onDeviceConfigChange(@NonNull Set<String> keys) {
if (keys.contains(KEY_SERVICE_ENABLED)) {
- mIsServiceEnabled = DeviceConfig.getBoolean(
- NAMESPACE_WEARABLE_SENSING,
- KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ mIsServiceEnabled =
+ DeviceConfig.getBoolean(
+ NAMESPACE_WEARABLE_SENSING,
+ KEY_SERVICE_ENABLED,
+ DEFAULT_SERVICE_ENABLED);
}
}
@Override
- protected WearableSensingManagerPerUserService newServiceLocked(int resolvedUserId,
- boolean disabled) {
+ protected WearableSensingManagerPerUserService newServiceLocked(
+ int resolvedUserId, boolean disabled) {
return new WearableSensingManagerPerUserService(this, mLock, resolvedUserId);
}
@@ -181,8 +184,8 @@ public class WearableSensingManagerService extends
@Override
protected void enforceCallingPermissionForManagement() {
- getContext().enforceCallingPermission(
- Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+ getContext()
+ .enforceCallingPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
}
@Override
@@ -193,9 +196,7 @@ public class WearableSensingManagerService extends
return MAX_TEMPORARY_SERVICE_DURATION_MS;
}
- /**
- * Returns the AmbientContextManagerPerUserService component for this user.
- */
+ /** Returns the AmbientContextManagerPerUserService component for this user. */
public ComponentName getComponentName(@UserIdInt int userId) {
synchronized (mLock) {
final WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
@@ -260,8 +261,8 @@ public class WearableSensingManagerService extends
*
* <p>The current rate limit will also be reset.
*
- * <p>This method is only used for testing and must not be called in production code because
- * it effectively bypasses the rate limiting introduced to enhance privacy protection.
+ * <p>This method is only used for testing and must not be called in production code because it
+ * effectively bypasses the rate limiting introduced to enhance privacy protection.
*/
@VisibleForTesting
void setDataRequestRateLimitWindowSize(@NonNull Duration windowSize) {
@@ -269,7 +270,7 @@ public class WearableSensingManagerService extends
TAG,
TextUtils.formatSimple(
"Setting the data request rate limit window size to %s. This also resets"
- + " the current limit and should only be callable from a test.",
+ + " the current limit and should only be callable from a test.",
windowSize));
mDataRequestRateLimiter =
new MultiRateLimiter.Builder(mContext)
@@ -282,8 +283,8 @@ public class WearableSensingManagerService extends
*
* <p>The current rate limit will also be reset.
*
- * <p>This method is only used for testing and must not be called in production code because
- * it effectively bypasses the rate limiting introduced to enhance privacy protection.
+ * <p>This method is only used for testing and must not be called in production code because it
+ * effectively bypasses the rate limiting introduced to enhance privacy protection.
*/
@VisibleForTesting
void resetDataRequestRateLimitWindowSize() {
@@ -391,14 +392,16 @@ public class WearableSensingManagerService extends
private void callPerUserServiceIfExist(
Consumer<WearableSensingManagerPerUserService> serviceConsumer,
- RemoteCallback statusCallback) {
+ @Nullable RemoteCallback statusCallback) {
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
if (service == null) {
Slog.w(TAG, "Service not available for userId " + userId);
- WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
- WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ if (statusCallback != null) {
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ }
return;
}
serviceConsumer.accept(service);
@@ -408,6 +411,16 @@ public class WearableSensingManagerService extends
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
@Override
+ public int getAvailableConnectionCount() {
+ WearableSensingManagerPerUserService service =
+ validateAndGetPerUserService(/* statusCallback= */ null);
+ if (service == null) {
+ return 0;
+ }
+ return service.getAvailableConnectionCount();
+ }
+
+ @Override
public void provideConnection(
ParcelFileDescriptor wearableConnection,
IWearableSensingCallback wearableSensingCallback,
@@ -431,6 +444,63 @@ public class WearableSensingManagerService extends
}
@Override
+ public int provideConcurrentConnection(
+ ParcelFileDescriptor wearableConnection,
+ PersistableBundle metadata,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
+ Slog.i(TAG, "WearableSensingManagerInternal provideConcurrentConnection.");
+ Objects.requireNonNull(wearableConnection);
+ Objects.requireNonNull(metadata);
+ Objects.requireNonNull(wearableSensingCallback);
+ Objects.requireNonNull(statusCallback);
+ WearableSensingManagerPerUserService service =
+ validateAndGetPerUserService(statusCallback);
+ if (service == null) {
+ return WearableSensingManager.CONNECTION_ID_INVALID;
+ }
+ return service.onProvideConcurrentConnection(
+ wearableConnection, metadata, wearableSensingCallback, statusCallback);
+ }
+
+ @Override
+ public boolean removeConnection(int connectionId) {
+ Slog.i(TAG, "WearableSensingManagerInternal removeConnection.");
+ WearableSensingManagerPerUserService service =
+ validateAndGetPerUserService(/* statusCallback= */ null);
+ if (service == null) {
+ return false;
+ }
+ return service.removeConnection(connectionId);
+ }
+
+ @Override
+ public void removeAllConnections() {
+ Slog.i(TAG, "WearableSensingManagerInternal removeAllConnections.");
+ WearableSensingManagerPerUserService service =
+ validateAndGetPerUserService(/* statusCallback= */ null);
+ if (service == null) {
+ return;
+ }
+ service.removeAllConnections();
+ }
+
+ @Override
+ public void provideReadOnlyParcelFileDescriptor(
+ ParcelFileDescriptor parcelFileDescriptor,
+ PersistableBundle metadata,
+ RemoteCallback statusCallback) {
+ Slog.i(TAG, "WearableSensingManagerInternal provideReadOnlyParcelFileDescriptor.");
+ WearableSensingManagerPerUserService service =
+ validateAndGetPerUserService(statusCallback);
+ if (service == null) {
+ return;
+ }
+ service.onProvideReadOnlyParcelFileDescriptor(
+ parcelFileDescriptor, metadata, statusCallback);
+ }
+
+ @Override
public void provideDataStream(
ParcelFileDescriptor parcelFileDescriptor,
@Nullable IWearableSensingCallback wearableSensingCallback,
@@ -455,9 +525,7 @@ public class WearableSensingManagerService extends
@Override
public void provideData(
- PersistableBundle data,
- SharedMemory sharedMemory,
- RemoteCallback callback) {
+ PersistableBundle data, SharedMemory sharedMemory, RemoteCallback callback) {
Slog.d(TAG, "WearableSensingManagerInternal provideData.");
Objects.requireNonNull(data);
Objects.requireNonNull(callback);
@@ -470,8 +538,7 @@ public class WearableSensingManagerService extends
return;
}
callPerUserServiceIfExist(
- service -> service.onProvidedData(data, sharedMemory, callback),
- callback);
+ service -> service.onProvidedData(data, sharedMemory, callback), callback);
}
@Override
@@ -601,10 +668,44 @@ public class WearableSensingManagerService extends
}
@Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
- this, in, out, err, args, callback, resultReceiver);
+ public void onShellCommand(
+ FileDescriptor in,
+ FileDescriptor out,
+ FileDescriptor err,
+ String[] args,
+ ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ new WearableSensingShellCommand(WearableSensingManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ @Nullable
+ private WearableSensingManagerPerUserService validateAndGetPerUserService(
+ @Nullable RemoteCallback statusCallback) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ if (statusCallback != null) {
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ }
+ return null;
+ }
+ int userId = UserHandle.getCallingUserId();
+ WearableSensingManagerPerUserService service;
+ synchronized (mLock) {
+ service = getServiceForUserLocked(userId);
+ }
+ if (service == null) {
+ Slog.w(TAG, "Service not available for userId " + userId);
+ if (statusCallback != null) {
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ }
+ return null;
+ }
+ return service;
}
}
}
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 67401530763b..179866153b5a 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -16,8 +16,6 @@
package com.android.server.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -36,12 +34,14 @@ import android.util.Log;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewZygote;
import com.android.internal.util.XmlUtils;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
+import com.android.server.pinner.PinnedFile;
+import com.android.server.pinner.PinnerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -247,37 +247,13 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo) {
- return UserPackage.getPackageInfosAllUsers(mContext, configInfo.packageName, PACKAGE_FLAGS);
+ public boolean isCompatibleImplementationPackage(PackageInfo packageInfo) {
+ return WebViewFactoryProvider.isCompatibleImplementationPackage(packageInfo);
}
@Override
- public int getMultiProcessSetting() {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "getMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
- }
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
- }
-
- @Override
- public void setMultiProcessSetting(int value) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "setMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
- }
- Settings.Global.putInt(
- mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
- }
-
- @Override
- public void notifyZygote(boolean enableMultiProcess) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "notifyZygote shouldn't be called if update_service_v2 flag is set.");
- }
- WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
+ public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo) {
+ return UserPackage.getPackageInfosAllUsers(mContext, configInfo.packageName, PACKAGE_FLAGS);
}
@Override
@@ -286,12 +262,6 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public boolean isMultiProcessDefaultEnabled() {
- // Multiprocess is enabled by default for all devices.
- return true;
- }
-
- @Override
public void pinWebviewIfRequired(ApplicationInfo appInfo) {
PinnerService pinnerService = LocalServices.getService(PinnerService.class);
int webviewPinQuota = pinnerService.getWebviewPinQuota();
@@ -318,8 +288,9 @@ public class SystemImpl implements SystemInterface {
if (webviewPinQuota <= 0) {
break;
}
- int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP);
- webviewPinQuota -= bytesPinned;
+ PinnedFile pf = pinnerService.pinFile(
+ apk, webviewPinQuota, appInfo, PIN_GROUP, /*pinOptimizedDeps=*/true);
+ webviewPinQuota -= pf.bytesPinned;
}
}
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 3b77d07412ce..d9e1a3a5e02d 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -47,6 +47,9 @@ public interface SystemInterface {
boolean systemIsDebuggable();
PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
+ /** Check if the given package is a compatible WebView implementation for the OS. */
+ boolean isCompatibleImplementationPackage(PackageInfo packageInfo);
+
/**
* Get the PackageInfos of all users for the package represented by {@param configInfo}.
* @return an array of UserPackages for a certain package, each UserPackage being belonging to a
@@ -55,12 +58,8 @@ public interface SystemInterface {
*/
List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo);
- int getMultiProcessSetting();
- void setMultiProcessSetting(int value);
- void notifyZygote(boolean enableMultiProcess);
/** Start the zygote if it's not already running. */
void ensureZygoteStarted();
- boolean isMultiProcessDefaultEnabled();
void pinWebviewIfRequired(ApplicationInfo appInfo);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 6b936077cc73..e1fa8848b0e1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,8 +16,6 @@
package com.android.server.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -63,7 +61,7 @@ public class WebViewUpdateService extends SystemService {
new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
private BroadcastReceiver mWebViewUpdatedReceiver;
- private WebViewUpdateServiceInterface mImpl;
+ private WebViewUpdateServiceImpl2 mImpl;
static final int PACKAGE_CHANGED = 0;
static final int PACKAGE_ADDED = 1;
@@ -72,11 +70,7 @@ public class WebViewUpdateService extends SystemService {
public WebViewUpdateService(Context context) {
super(context);
- if (updateServiceV2()) {
- mImpl = new WebViewUpdateServiceImpl2(new SystemImpl(context));
- } else {
- mImpl = new WebViewUpdateServiceImpl(new SystemImpl(context));
- }
+ mImpl = new WebViewUpdateServiceImpl2(new SystemImpl(context));
}
@Override
@@ -175,13 +169,8 @@ public class WebViewUpdateService extends SystemService {
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- if (updateServiceV2()) {
- (new WebViewUpdateServiceShellCommand2(this))
- .exec(this, in, out, err, args, callback, resultReceiver);
- } else {
- (new WebViewUpdateServiceShellCommand(this))
- .exec(this, in, out, err, args, callback, resultReceiver);
- }
+ (new WebViewUpdateServiceShellCommand2(this))
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -305,45 +294,6 @@ public class WebViewUpdateService extends SystemService {
return currentWebViewPackage;
}
- @Override // Binder call
- public boolean isMultiProcessEnabled() {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is"
- + " set.");
- }
- return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
- }
-
- @Override // Binder call
- public void enableMultiProcess(boolean enable) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "enableMultiProcess shouldn't be called if update_service_v2 flag is set.");
- }
- if (getContext()
- .checkCallingPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- String msg =
- "Permission Denial: enableMultiProcess() from pid="
- + Binder.getCallingPid()
- + ", uid="
- + Binder.getCallingUid()
- + " requires "
- + android.Manifest.permission.WRITE_SECURE_SETTINGS;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
-
- final long callingId = Binder.clearCallingIdentity();
- try {
- WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
deleted file mode 100644
index b9be4a2deef2..000000000000
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ /dev/null
@@ -1,768 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.webkit;
-
-import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.Signature;
-import android.os.AsyncTask;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.AndroidRuntimeException;
-import android.util.Slog;
-import android.webkit.UserPackage;
-import android.webkit.WebViewFactory;
-import android.webkit.WebViewProviderInfo;
-import android.webkit.WebViewProviderResponse;
-
-import com.android.modules.expresslog.Counter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of the WebViewUpdateService.
- * This class doesn't depend on the android system like the actual Service does and can be used
- * directly by tests (as long as they implement a SystemInterface).
- *
- * This class keeps track of and prepares the current WebView implementation, and needs to keep
- * track of a couple of different things such as what package is used as WebView implementation.
- *
- * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
- * thread or on one of multiple Binder threads. The WebView preparation code shares state between
- * threads meaning that code that chooses a new WebView implementation or checks which
- * implementation is being used needs to hold a lock.
- *
- * The WebViewUpdateService can be accessed in a couple of different ways.
- * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
- * as the WebView preparation class.
- * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
- * and the WebViewUpdateService should not have been accessed before this call. In this call we
- * choose WebView implementation for the first time.
- * 3. The update service listens for Intents related to package installs and removals. These intents
- * are received and processed on the UI thread. Each intent can result in changing WebView
- * implementation.
- * 4. The update service can be reached through Binder calls which are handled on specific binder
- * threads. These calls can be made from any process. Generally they are used for changing WebView
- * implementation (from Settings), getting information about the current WebView implementation (for
- * loading WebView into an app process), or notifying the service about Relro creation being
- * completed.
- *
- * @hide
- */
-class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
- private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
-
- private static class WebViewPackageMissingException extends Exception {
- WebViewPackageMissingException(String message) {
- super(message);
- }
-
- WebViewPackageMissingException(Exception e) {
- super(e);
- }
- }
-
- private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
- private static final long NS_PER_MS = 1000000;
-
- private static final int VALIDITY_OK = 0;
- private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
- private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
- private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
- private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
-
- private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
- private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
-
- private final SystemInterface mSystemInterface;
-
- private long mMinimumVersionCode = -1;
-
- // Keeps track of the number of running relro creations
- private int mNumRelroCreationsStarted = 0;
- private int mNumRelroCreationsFinished = 0;
- // Implies that we need to rerun relro creation because we are using an out-of-date package
- private boolean mWebViewPackageDirty = false;
- private boolean mAnyWebViewInstalled = false;
-
- private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
-
- // The WebView package currently in use (or the one we are preparing).
- private PackageInfo mCurrentWebViewPackage = null;
-
- private final Object mLock = new Object();
-
- WebViewUpdateServiceImpl(SystemInterface systemInterface) {
- mSystemInterface = systemInterface;
- }
-
- @Override
- public void packageStateChanged(String packageName, int changedState, int userId) {
- // We don't early out here in different cases where we could potentially early-out (e.g. if
- // we receive PACKAGE_CHANGED for another user than the system user) since that would
- // complicate this logic further and open up for more edge cases.
- for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
- String webviewPackage = provider.packageName;
-
- if (webviewPackage.equals(packageName)) {
- boolean updateWebView = false;
- boolean removedOrChangedOldPackage = false;
- String oldProviderName = null;
- PackageInfo newPackage = null;
- synchronized (mLock) {
- try {
- newPackage = findPreferredWebViewPackage();
- if (mCurrentWebViewPackage != null) {
- oldProviderName = mCurrentWebViewPackage.packageName;
- }
- // Only trigger update actions if the updated package is the one
- // that will be used, or the one that was in use before the
- // update, or if we haven't seen a valid WebView package before.
- updateWebView =
- provider.packageName.equals(newPackage.packageName)
- || provider.packageName.equals(oldProviderName)
- || mCurrentWebViewPackage == null;
- // We removed the old package if we received an intent to remove
- // or replace the old package.
- removedOrChangedOldPackage =
- provider.packageName.equals(oldProviderName);
- if (updateWebView) {
- onWebViewProviderChanged(newPackage);
- }
- } catch (WebViewPackageMissingException e) {
- mCurrentWebViewPackage = null;
- Slog.e(TAG, "Could not find valid WebView package to create relro with "
- + e);
- }
- }
- if (updateWebView && !removedOrChangedOldPackage
- && oldProviderName != null) {
- // If the provider change is the result of adding or replacing a
- // package that was not the previous provider then we must kill
- // packages dependent on the old package ourselves. The framework
- // only kills dependents of packages that are being removed.
- mSystemInterface.killPackageDependents(oldProviderName);
- }
- return;
- }
- }
- }
-
- @Override
- public void prepareWebViewInSystemServer() {
- mSystemInterface.notifyZygote(isMultiProcessEnabled());
- try {
- synchronized (mLock) {
- mCurrentWebViewPackage = findPreferredWebViewPackage();
- String userSetting = mSystemInterface.getUserChosenWebViewProvider();
- if (userSetting != null
- && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
- // Don't persist the user-chosen setting across boots if the package being
- // chosen is not used (could be disabled or uninstalled) so that the user won't
- // be surprised by the device switching to using a certain webview package,
- // that was uninstalled/disabled a long time ago, if it is installed/enabled
- // again.
- mSystemInterface.updateUserSetting(mCurrentWebViewPackage.packageName);
- }
- onWebViewProviderChanged(mCurrentWebViewPackage);
- }
- } catch (WebViewPackageMissingException e) {
- Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
- } catch (Throwable t) {
- // We don't know a case when this should happen but we log and discard errors at this
- // stage as we must not crash the system server.
- Slog.wtf(TAG, "error preparing webview provider from system server", t);
- }
-
- if (getCurrentWebViewPackage() == null) {
- // We didn't find a valid WebView implementation. Try explicitly re-enabling the
- // fallback package for all users in case it was disabled, even if we already did the
- // one-time migration before. If this actually changes the state, we will see the
- // PackageManager broadcast shortly and try again.
- WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
- if (fallbackProvider != null) {
- Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
- mSystemInterface.enablePackageForAllUsers(fallbackProvider.packageName, true);
- } else {
- Slog.e(TAG, "No valid provider and no fallback available.");
- }
- }
- }
-
- private void startZygoteWhenReady() {
- // Wait on a background thread for RELRO creation to be done. We ignore the return value
- // because even if RELRO creation failed we still want to start the zygote.
- waitForAndGetProvider();
- mSystemInterface.ensureZygoteStarted();
- }
-
- @Override
- public void handleNewUser(int userId) {
- // The system user is always started at boot, and by that point we have already run one
- // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
- // out here.
- if (userId == UserHandle.USER_SYSTEM) return;
- handleUserChange();
- }
-
- @Override
- public void handleUserRemoved(int userId) {
- handleUserChange();
- }
-
- /**
- * Called when a user was added or removed to ensure WebView preparation is triggered.
- * This has to be done since the WebView package we use depends on the enabled-state
- * of packages for all users (so adding or removing a user might cause us to change package).
- */
- private void handleUserChange() {
- // Potentially trigger package-changing logic.
- updateCurrentWebViewPackage(null);
- }
-
- @Override
- public void notifyRelroCreationCompleted() {
- synchronized (mLock) {
- mNumRelroCreationsFinished++;
- checkIfRelrosDoneLocked();
- }
- }
-
- @Override
- public WebViewProviderResponse waitForAndGetProvider() {
- PackageInfo webViewPackage = null;
- final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
- boolean webViewReady = false;
- int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
- synchronized (mLock) {
- webViewReady = webViewIsReadyLocked();
- while (!webViewReady) {
- final long timeNowMs = System.nanoTime() / NS_PER_MS;
- if (timeNowMs >= timeoutTimeMs) break;
- try {
- mLock.wait(timeoutTimeMs - timeNowMs);
- } catch (InterruptedException e) {
- // ignore
- }
- webViewReady = webViewIsReadyLocked();
- }
- // Make sure we return the provider that was used to create the relro file
- webViewPackage = mCurrentWebViewPackage;
- if (webViewReady) {
- // success
- } else if (!mAnyWebViewInstalled) {
- webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
- } else {
- // Either the current relro creation isn't done yet, or the new relro creatioin
- // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
- webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
- String timeoutError = "Timed out waiting for relro creation, relros started "
- + mNumRelroCreationsStarted
- + " relros finished " + mNumRelroCreationsFinished
- + " package dirty? " + mWebViewPackageDirty;
- Slog.e(TAG, timeoutError);
- Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError);
- }
- }
- if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
- return new WebViewProviderResponse(webViewPackage, webViewStatus);
- }
-
- /**
- * Change WebView provider and provider setting and kill packages using the old provider.
- * Return the new provider (in case we are in the middle of creating relro files, or
- * replacing that provider it will not be in use directly, but will be used when the relros
- * or the replacement are done).
- */
- @Override
- public String changeProviderAndSetting(String newProviderName) {
- PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
- if (newPackage == null) return "";
- return newPackage.packageName;
- }
-
- /**
- * Update the current WebView package.
- * @param newProviderName the package to switch to, null if no package has been explicitly
- * chosen.
- */
- private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
- PackageInfo oldPackage = null;
- PackageInfo newPackage = null;
- boolean providerChanged = false;
- synchronized (mLock) {
- oldPackage = mCurrentWebViewPackage;
-
- if (newProviderName != null) {
- mSystemInterface.updateUserSetting(newProviderName);
- }
-
- try {
- newPackage = findPreferredWebViewPackage();
- providerChanged = (oldPackage == null)
- || !newPackage.packageName.equals(oldPackage.packageName);
- } catch (WebViewPackageMissingException e) {
- // If updated the Setting but don't have an installed WebView package, the
- // Setting will be used when a package is available.
- mCurrentWebViewPackage = null;
- Slog.e(TAG, "Couldn't find WebView package to use " + e);
- return null;
- }
- // Perform the provider change if we chose a new provider
- if (providerChanged) {
- onWebViewProviderChanged(newPackage);
- }
- }
- // Kill apps using the old provider only if we changed provider
- if (providerChanged && oldPackage != null) {
- mSystemInterface.killPackageDependents(oldPackage.packageName);
- }
- // Return the new provider, this is not necessarily the one we were asked to switch to,
- // but the persistent setting will now be pointing to the provider we were asked to
- // switch to anyway.
- return newPackage;
- }
-
- /**
- * This is called when we change WebView provider, either when the current provider is
- * updated or a new provider is chosen / takes precedence.
- */
- private void onWebViewProviderChanged(PackageInfo newPackage) {
- synchronized (mLock) {
- mAnyWebViewInstalled = true;
- if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
- mSystemInterface.pinWebviewIfRequired(newPackage.applicationInfo);
- mCurrentWebViewPackage = newPackage;
-
- // The relro creations might 'finish' (not start at all) before
- // WebViewFactory.onWebViewProviderChanged which means we might not know the
- // number of started creations before they finish.
- mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
- mNumRelroCreationsFinished = 0;
- mNumRelroCreationsStarted =
- mSystemInterface.onWebViewProviderChanged(newPackage);
- Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
- if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
- Counter.logIncrement(
- "webview.value_on_webview_provider_changed_"
- + "with_default_package_counter");
- }
- // If the relro creations finish before we know the number of started creations
- // we will have to do any cleanup/notifying here.
- checkIfRelrosDoneLocked();
- } else {
- mWebViewPackageDirty = true;
- }
- }
-
- // Once we've notified the system that the provider has changed and started RELRO creation,
- // try to restart the zygote so that it will be ready when apps use it.
- if (isMultiProcessEnabled()) {
- AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
- }
- }
-
- /**
- * Fetch only the currently valid WebView packages.
- **/
- @Override
- public WebViewProviderInfo[] getValidWebViewPackages() {
- ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
- WebViewProviderInfo[] providers =
- new WebViewProviderInfo[providersAndPackageInfos.length];
- for (int n = 0; n < providersAndPackageInfos.length; n++) {
- providers[n] = providersAndPackageInfos[n].provider;
- }
- return providers;
- }
-
- @Override
- public WebViewProviderInfo getDefaultWebViewPackage() {
- for (WebViewProviderInfo provider : getWebViewPackages()) {
- if (provider.availableByDefault) {
- return provider;
- }
- }
-
- // This should be unreachable because the config parser enforces that there is at least
- // one availableByDefault provider.
- throw new AndroidRuntimeException("No available by default WebView Provider.");
- }
-
- private static class ProviderAndPackageInfo {
- public final WebViewProviderInfo provider;
- public final PackageInfo packageInfo;
-
- ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
- this.provider = provider;
- this.packageInfo = packageInfo;
- }
- }
-
- private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
- WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
- List<ProviderAndPackageInfo> providers = new ArrayList<>();
- for (int n = 0; n < allProviders.length; n++) {
- try {
- PackageInfo packageInfo =
- mSystemInterface.getPackageInfoForProvider(allProviders[n]);
- if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
- providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
- }
- } catch (NameNotFoundException e) {
- // Don't add non-existent packages
- }
- }
- return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
- }
-
- /**
- * Returns either the package info of the WebView provider determined in the following way:
- * If the user has chosen a provider then use that if it is valid,
- * otherwise use the first package in the webview priority list that is valid.
- *
- */
- private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
- ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
-
- String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider();
-
- // If the user has chosen provider, use that (if it's installed and enabled for all
- // users).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
- // userPackages can contain null objects.
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(
- providerAndPackage.provider);
- if (isInstalledAndEnabledForAllUsers(userPackages)) {
- return providerAndPackage.packageInfo;
- }
- }
- }
-
- // User did not choose, or the choice failed; use the most stable provider that is
- // installed and enabled for all users, and available by default (not through
- // user choice).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.availableByDefault) {
- // userPackages can contain null objects.
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(
- providerAndPackage.provider);
- if (isInstalledAndEnabledForAllUsers(userPackages)) {
- return providerAndPackage.packageInfo;
- }
- }
- }
-
- // This should never happen during normal operation (only with modified system images).
- mAnyWebViewInstalled = false;
- throw new WebViewPackageMissingException("Could not find a loadable WebView package");
- }
-
- /**
- * Return true iff {@param packageInfos} point to only installed and enabled packages.
- * The given packages {@param packageInfos} should all be pointing to the same package, but each
- * PackageInfo representing a different user's package.
- */
- private static boolean isInstalledAndEnabledForAllUsers(
- List<UserPackage> userPackages) {
- for (UserPackage userPackage : userPackages) {
- if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public WebViewProviderInfo[] getWebViewPackages() {
- return mSystemInterface.getWebViewPackages();
- }
-
- @Override
- public PackageInfo getCurrentWebViewPackage() {
- synchronized (mLock) {
- return mCurrentWebViewPackage;
- }
- }
-
- /**
- * Returns whether WebView is ready and is not going to go through its preparation phase
- * again directly.
- */
- private boolean webViewIsReadyLocked() {
- return !mWebViewPackageDirty
- && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
- // The current package might be replaced though we haven't received an intent
- // declaring this yet, the following flag makes anyone loading WebView to wait in
- // this case.
- && mAnyWebViewInstalled;
- }
-
- private void checkIfRelrosDoneLocked() {
- if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
- if (mWebViewPackageDirty) {
- mWebViewPackageDirty = false;
- // If we have changed provider since we started the relro creation we need to
- // redo the whole process using the new package instead.
- try {
- PackageInfo newPackage = findPreferredWebViewPackage();
- onWebViewProviderChanged(newPackage);
- } catch (WebViewPackageMissingException e) {
- mCurrentWebViewPackage = null;
- // If we can't find any valid WebView package we are now in a state where
- // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
- // should simply wait until we receive an intent declaring a new package was
- // installed.
- }
- } else {
- mLock.notifyAll();
- }
- }
- }
-
- private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
- // Ensure the provider targets this framework release (or a later one).
- if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
- return VALIDITY_INCORRECT_SDK_VERSION;
- }
- if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
- && !mSystemInterface.systemIsDebuggable()) {
- // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
- // minimum version code. This check is only enforced for user builds.
- return VALIDITY_INCORRECT_VERSION_CODE;
- }
- if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
- return VALIDITY_INCORRECT_SIGNATURE;
- }
- if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
- return VALIDITY_NO_LIBRARY_FLAG;
- }
- return VALIDITY_OK;
- }
-
- /**
- * Both versionCodes should be from a WebView provider package implemented by Chromium.
- * VersionCodes from other kinds of packages won't make any sense in this method.
- *
- * An introduction to Chromium versionCode scheme:
- * "BBBBPPPXX"
- * BBBB: 4 digit branch number. It monotonically increases over time.
- * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
- * may change their meaning in the future.
- * XX: Digits to differentiate different APK builds of the same source version.
- *
- * This method takes the "BBBB" of versionCodes and compare them.
- *
- * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
- * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
- * is the canonical source for how Chromium versionCodes are calculated.
- *
- * @return true if versionCode1 is higher than or equal to versionCode2.
- */
- private static boolean versionCodeGE(long versionCode1, long versionCode2) {
- long v1 = versionCode1 / 100000;
- long v2 = versionCode2 / 100000;
-
- return v1 >= v2;
- }
-
- /**
- * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
- * of all available-by-default WebView provider packages. If there is no such WebView provider
- * package on the system, then return -1, which means all positive versionCode WebView packages
- * are accepted.
- *
- * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
- * shared between threads. Furthermore, this method does not hold mLock meaning that we must
- * take extra care to ensure this method is thread-safe.
- */
- private long getMinimumVersionCode() {
- if (mMinimumVersionCode > 0) {
- return mMinimumVersionCode;
- }
-
- long minimumVersionCode = -1;
- for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
- if (provider.availableByDefault) {
- try {
- long versionCode =
- mSystemInterface.getFactoryPackageVersion(provider.packageName);
- if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
- minimumVersionCode = versionCode;
- }
- } catch (NameNotFoundException e) {
- // Safe to ignore.
- }
- }
- }
-
- mMinimumVersionCode = minimumVersionCode;
- return mMinimumVersionCode;
- }
-
- private static boolean providerHasValidSignature(WebViewProviderInfo provider,
- PackageInfo packageInfo, SystemInterface systemInterface) {
- // Skip checking signatures on debuggable builds, for development purposes.
- if (systemInterface.systemIsDebuggable()) return true;
-
- // Allow system apps to be valid providers regardless of signature.
- if (packageInfo.applicationInfo.isSystemApp()) return true;
-
- // We don't support packages with multiple signatures.
- if (packageInfo.signatures.length != 1) return false;
-
- // If any of the declared signatures match the package signature, it's valid.
- for (Signature signature : provider.signatures) {
- if (signature.equals(packageInfo.signatures[0])) return true;
- }
-
- return false;
- }
-
- /**
- * Returns the only fallback provider in the set of given packages, or null if there is none.
- */
- private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
- for (WebViewProviderInfo provider : webviewPackages) {
- if (provider.isFallback) {
- return provider;
- }
- }
- return null;
- }
-
- @Override
- public boolean isMultiProcessEnabled() {
- int settingValue = mSystemInterface.getMultiProcessSetting();
- if (mSystemInterface.isMultiProcessDefaultEnabled()) {
- // Multiprocess should be enabled unless the user has turned it off manually.
- return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
- } else {
- // Multiprocess should not be enabled, unless the user has turned it on manually.
- return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
- }
- }
-
- @Override
- public void enableMultiProcess(boolean enable) {
- PackageInfo current = getCurrentWebViewPackage();
- mSystemInterface.setMultiProcessSetting(
- enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
- mSystemInterface.notifyZygote(enable);
- if (current != null) {
- mSystemInterface.killPackageDependents(current.packageName);
- }
- }
-
- /**
- * Dump the state of this Service.
- */
- @Override
- public void dumpState(PrintWriter pw) {
- pw.println("Current WebView Update Service state");
- pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
- synchronized (mLock) {
- if (mCurrentWebViewPackage == null) {
- pw.println(" Current WebView package is null");
- } else {
- pw.println(String.format(" Current WebView package (name, version): (%s, %s)",
- mCurrentWebViewPackage.packageName,
- mCurrentWebViewPackage.versionName));
- }
- pw.println(String.format(" Minimum targetSdkVersion: %d",
- UserPackage.MINIMUM_SUPPORTED_SDK));
- pw.println(String.format(" Minimum WebView version code: %d",
- mMinimumVersionCode));
- pw.println(String.format(" Number of relros started: %d",
- mNumRelroCreationsStarted));
- pw.println(String.format(" Number of relros finished: %d",
- mNumRelroCreationsFinished));
- pw.println(String.format(" WebView package dirty: %b", mWebViewPackageDirty));
- pw.println(String.format(" Any WebView package installed: %b",
- mAnyWebViewInstalled));
-
- try {
- PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
- pw.println(String.format(
- " Preferred WebView package (name, version): (%s, %s)",
- preferredWebViewPackage.packageName,
- preferredWebViewPackage.versionName));
- } catch (WebViewPackageMissingException e) {
- pw.println(String.format(" Preferred WebView package: none"));
- }
-
- dumpAllPackageInformationLocked(pw);
- }
- }
-
- private void dumpAllPackageInformationLocked(PrintWriter pw) {
- WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
- pw.println(" WebView packages:");
- for (WebViewProviderInfo provider : allProviders) {
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(provider);
- PackageInfo systemUserPackageInfo =
- userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
- if (systemUserPackageInfo == null) {
- pw.println(String.format(" %s is NOT installed.", provider.packageName));
- continue;
- }
-
- int validity = validityResult(provider, systemUserPackageInfo);
- String packageDetails = String.format(
- "versionName: %s, versionCode: %d, targetSdkVersion: %d",
- systemUserPackageInfo.versionName,
- systemUserPackageInfo.getLongVersionCode(),
- systemUserPackageInfo.applicationInfo.targetSdkVersion);
- if (validity == VALIDITY_OK) {
- boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
- mSystemInterface.getPackageInfoForProviderAllUsers(provider));
- pw.println(String.format(
- " Valid package %s (%s) is %s installed/enabled for all users",
- systemUserPackageInfo.packageName,
- packageDetails,
- installedForAllUsers ? "" : "NOT"));
- } else {
- pw.println(String.format(" Invalid package %s (%s), reason: %s",
- systemUserPackageInfo.packageName,
- packageDetails,
- getInvalidityReason(validity)));
- }
- }
- }
-
- private static String getInvalidityReason(int invalidityReason) {
- switch (invalidityReason) {
- case VALIDITY_INCORRECT_SDK_VERSION:
- return "SDK version too low";
- case VALIDITY_INCORRECT_VERSION_CODE:
- return "Version code too low";
- case VALIDITY_INCORRECT_SIGNATURE:
- return "Incorrect signature";
- case VALIDITY_NO_LIBRARY_FLAG:
- return "No WebView-library manifest flag";
- default:
- return "Unexcepted validity-reason";
- }
- }
-}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 307c15b72c76..9e8dc2690a9d 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -27,6 +27,7 @@ import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -66,7 +67,7 @@ import java.util.List;
*
* @hide
*/
-class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
+class WebViewUpdateServiceImpl2 {
private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName();
private static class WebViewPackageMissingException extends Exception {
@@ -79,7 +80,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
private static final long NS_PER_MS = 1000000;
private static final int VALIDITY_OK = 0;
- private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+ private static final int VALIDITY_OS_INCOMPATIBLE = 1;
private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
@@ -125,7 +126,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
mDefaultProvider = defaultProvider;
}
- @Override
public void packageStateChanged(String packageName, int changedState, int userId) {
// We don't early out here in different cases where we could potentially early-out (e.g. if
// we receive PACKAGE_CHANGED for another user than the system user) since that would
@@ -216,7 +216,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
mSystemInterface.enablePackageForAllUsers(mDefaultProvider.packageName, true);
}
- @Override
public void prepareWebViewInSystemServer() {
try {
boolean repairNeeded = true;
@@ -256,7 +255,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
mSystemInterface.ensureZygoteStarted();
}
- @Override
public void handleNewUser(int userId) {
// The system user is always started at boot, and by that point we have already run one
// round of the package-changing logic (through prepareWebViewInSystemServer()), so early
@@ -265,7 +263,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
handleUserChange();
}
- @Override
public void handleUserRemoved(int userId) {
handleUserChange();
}
@@ -280,7 +277,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
updateCurrentWebViewPackage(null);
}
- @Override
public void notifyRelroCreationCompleted() {
synchronized (mLock) {
mNumRelroCreationsFinished++;
@@ -288,7 +284,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
}
}
- @Override
public WebViewProviderResponse waitForAndGetProvider() {
PackageInfo webViewPackage = null;
final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
@@ -334,7 +329,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
* replacing that provider it will not be in use directly, but will be used when the relros
* or the replacement are done).
*/
- @Override
public String changeProviderAndSetting(String newProviderName) {
PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
if (newPackage == null) return "";
@@ -430,7 +424,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
}
/** Fetch only the currently valid WebView packages. */
- @Override
public WebViewProviderInfo[] getValidWebViewPackages() {
ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
WebViewProviderInfo[] providers =
@@ -445,7 +438,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
* Returns the default WebView provider which should be first availableByDefault option in the
* system config.
*/
- @Override
public WebViewProviderInfo getDefaultWebViewPackage() {
return mDefaultProvider;
}
@@ -550,12 +542,10 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
return true;
}
- @Override
public WebViewProviderInfo[] getWebViewPackages() {
return mSystemInterface.getWebViewPackages();
}
- @Override
public PackageInfo getCurrentWebViewPackage() {
synchronized (mLock) {
return mCurrentWebViewPackage;
@@ -598,9 +588,9 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
}
private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
- // Ensure the provider targets this framework release (or a later one).
- if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
- return VALIDITY_INCORRECT_SDK_VERSION;
+ // Ensure the provider is compatible with this framework release.
+ if (!mSystemInterface.isCompatibleImplementationPackage(packageInfo)) {
+ return VALIDITY_OS_INCOMPATIBLE;
}
if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
&& !mSystemInterface.systemIsDebuggable()) {
@@ -708,20 +698,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
return null;
}
- @Override
- public boolean isMultiProcessEnabled() {
- throw new IllegalStateException(
- "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is set.");
- }
-
- @Override
- public void enableMultiProcess(boolean enable) {
- throw new IllegalStateException(
- "enableMultiProcess shouldn't be called if update_service_v2 flag is set.");
- }
-
/** Dump the state of this Service. */
- @Override
public void dumpState(PrintWriter pw) {
pw.println("Current WebView Update Service state");
synchronized (mLock) {
@@ -736,7 +713,8 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
}
pw.println(
TextUtils.formatSimple(
- " Minimum targetSdkVersion: %d", UserPackage.MINIMUM_SUPPORTED_SDK));
+ " %s",
+ WebViewFactoryProvider.describeCompatibleImplementationPackage()));
pw.println(
TextUtils.formatSimple(
" Minimum WebView version code: %d", mMinimumVersionCode));
@@ -810,8 +788,8 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
private static String getInvalidityReason(int invalidityReason) {
switch (invalidityReason) {
- case VALIDITY_INCORRECT_SDK_VERSION:
- return "SDK version too low";
+ case VALIDITY_OS_INCOMPATIBLE:
+ return "Not compatible with this OS version";
case VALIDITY_INCORRECT_VERSION_CODE:
return "Version code too low";
case VALIDITY_INCORRECT_SIGNATURE:
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
deleted file mode 100644
index 1772ef9c7405..000000000000
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.webkit;
-
-import android.content.pm.PackageInfo;
-import android.webkit.WebViewProviderInfo;
-import android.webkit.WebViewProviderResponse;
-
-import java.io.PrintWriter;
-
-interface WebViewUpdateServiceInterface {
- void packageStateChanged(String packageName, int changedState, int userId);
-
- void handleNewUser(int userId);
-
- void handleUserRemoved(int userId);
-
- WebViewProviderInfo[] getWebViewPackages();
-
- void prepareWebViewInSystemServer();
-
- void notifyRelroCreationCompleted();
-
- WebViewProviderResponse waitForAndGetProvider();
-
- String changeProviderAndSetting(String newProviderName);
-
- WebViewProviderInfo[] getValidWebViewPackages();
-
- WebViewProviderInfo getDefaultWebViewPackage();
-
- PackageInfo getCurrentWebViewPackage();
-
- boolean isMultiProcessEnabled();
-
- void enableMultiProcess(boolean enable);
-
- void dumpState(PrintWriter pw);
-}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java
deleted file mode 100644
index 7529c4157101..000000000000
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.webkit;
-
-import android.os.RemoteException;
-import android.os.ShellCommand;
-import android.webkit.IWebViewUpdateService;
-
-import java.io.PrintWriter;
-
-class WebViewUpdateServiceShellCommand extends ShellCommand {
- final IWebViewUpdateService mInterface;
-
- WebViewUpdateServiceShellCommand(IWebViewUpdateService service) {
- mInterface = service;
- }
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
- }
-
- final PrintWriter pw = getOutPrintWriter();
- try {
- switch(cmd) {
- case "set-webview-implementation":
- return setWebViewImplementation();
- case "enable-multiprocess":
- return enableMultiProcess(true);
- case "disable-multiprocess":
- return enableMultiProcess(false);
- default:
- return handleDefaultCommands(cmd);
- }
- } catch (RemoteException e) {
- pw.println("Remote exception: " + e);
- }
- return -1;
- }
-
- private int setWebViewImplementation() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- String shellChosenPackage = getNextArg();
- if (shellChosenPackage == null) {
- pw.println("Failed to switch, no PACKAGE provided.");
- pw.println("");
- helpSetWebViewImplementation();
- return 1;
- }
- String newPackage = mInterface.changeProviderAndSetting(shellChosenPackage);
- if (shellChosenPackage.equals(newPackage)) {
- pw.println("Success");
- return 0;
- } else {
- pw.println(String.format(
- "Failed to switch to %s, the WebView implementation is now provided by %s.",
- shellChosenPackage, newPackage));
- return 1;
- }
- }
-
- private int enableMultiProcess(boolean enable) throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- mInterface.enableMultiProcess(enable);
- pw.println("Success");
- return 0;
- }
-
- public void helpSetWebViewImplementation() {
- PrintWriter pw = getOutPrintWriter();
- pw.println(" set-webview-implementation PACKAGE");
- pw.println(" Set the WebView implementation to the specified package.");
- }
-
- @Override
- public void onHelp() {
- PrintWriter pw = getOutPrintWriter();
- pw.println("WebView updater commands:");
- pw.println(" help");
- pw.println(" Print this help text.");
- pw.println("");
- helpSetWebViewImplementation();
- pw.println(" enable-multiprocess");
- pw.println(" Enable multi-process mode for WebView");
- pw.println(" disable-multiprocess");
- pw.println(" Disable multi-process mode for WebView");
- pw.println();
- }
-}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 7cbacd6b0b82..dd769173fb34 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -22,11 +22,9 @@ import static android.os.Build.IS_USER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
-import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
@@ -50,23 +48,15 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowTracingLegacy.WINSCOPE_EXT;
import android.accessibilityservice.AccessibilityTrace;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
-import android.graphics.BLASTBufferQueue;
-import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.Path;
-import android.graphics.PixelFormat;
import android.graphics.Point;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
@@ -84,25 +74,16 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.MagnificationSpec;
import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManager.TransitionFlags;
import android.view.WindowManager.TransitionType;
-import android.view.WindowManagerPolicyConstants;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.TraceBuffer;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -112,7 +93,6 @@ import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
-import com.android.window.flags.Flags;
import java.io.File;
import java.io.IOException;
@@ -302,36 +282,6 @@ final class AccessibilityController {
}
}
- /** It is only used by unit test. */
- @VisibleForTesting
- Surface forceShowMagnifierSurface(int displayId) {
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier != null) {
- displayMagnifier.mMagnifiedViewport.mWindow.setAlpha(DisplayMagnifier.MagnifiedViewport
- .ViewportWindow.AnimationController.MAX_ALPHA);
- return displayMagnifier.mMagnifiedViewport.mWindow.mSurface;
- }
- return null;
- }
-
- void onWindowLayersChanged(int displayId) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
- | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".onWindowLayersChanged",
- FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
- "displayId=" + displayId);
- }
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier != null) {
- displayMagnifier.onWindowLayersChanged();
- }
- final WindowsForAccessibilityObserver windowsForA11yObserver =
- mWindowsForAccessibilityObserver.get(displayId);
- if (windowsForA11yObserver != null) {
- windowsForA11yObserver.scheduleComputeChangedWindows();
- }
- }
-
void onDisplaySizeChanged(DisplayContent displayContent) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
@@ -564,9 +514,6 @@ final class AccessibilityController {
}
void dump(PrintWriter pw, String prefix) {
- dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display",
- (index, key) -> pw.printf("%sDisplay #%d:", prefix + " ", key),
- dm -> dm.dump(pw, ""));
dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver,
"windows for accessibility observer");
mAccessibilityWindowsPopulator.dump(pw, prefix);
@@ -624,7 +571,6 @@ final class AccessibilityController {
private final Context mDisplayContext;
private final WindowManagerService mService;
- private final MagnifiedViewport mMagnifiedViewport;
private final Handler mHandler;
private final DisplayContent mDisplayContent;
private final Display mDisplay;
@@ -661,8 +607,6 @@ final class AccessibilityController {
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mUserContextChangedNotifier = new UserContextChangedNotifier(mHandler);
- mMagnifiedViewport = Flags.alwaysDrawMagnificationFullscreenBorder()
- ? null : new MagnifiedViewport();
mAccessibilityTracing =
AccessibilityController.getAccessibilityControllerInternal(mService);
mLongAnimationDuration = mDisplayContext.getResources().getInteger(
@@ -704,10 +648,6 @@ final class AccessibilityController {
} else {
mMagnificationSpec.clear();
}
-
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.setShowMagnifiedBorderIfNeeded();
- }
}
void setFullscreenMagnificationActivated(boolean activated) {
@@ -716,10 +656,6 @@ final class AccessibilityController {
FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
}
mIsFullscreenMagnificationActivated = activated;
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.setMagnifiedRegionBorderShown(activated, true);
- mMagnifiedViewport.showMagnificationBoundsIfNeeded();
- }
}
boolean isFullscreenMagnificationActivated() {
@@ -730,18 +666,6 @@ final class AccessibilityController {
return mIsFullscreenMagnificationActivated;
}
- void onWindowLayersChanged() {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(
- LOG_TAG + ".onWindowLayersChanged", FLAGS_MAGNIFICATION_CALLBACK);
- }
- if (DEBUG_LAYERS) {
- Slog.i(LOG_TAG, "Layers changed.");
- }
- recomputeBounds();
- mService.scheduleAnimationLocked();
- }
-
void onDisplaySizeChanged(DisplayContent displayContent) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".onDisplaySizeChanged",
@@ -754,9 +678,6 @@ final class AccessibilityController {
}
recomputeBounds();
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.onDisplaySizeChanged();
- }
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
@@ -927,10 +848,6 @@ final class AccessibilityController {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
}
-
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.destroyWindow();
- }
}
void recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded() {
@@ -940,10 +857,6 @@ final class AccessibilityController {
FLAGS_MAGNIFICATION_CALLBACK);
}
recomputeBounds();
-
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.drawWindowIfNeeded();
- }
}
void recomputeBounds() {
@@ -1051,16 +964,9 @@ final class AccessibilityController {
}
visibleWindows.clear();
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
- }
-
final boolean magnifiedChanged =
!mOldMagnificationRegion.equals(mMagnificationRegion);
if (magnifiedChanged) {
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
- }
mOldMagnificationRegion.set(mMagnificationRegion);
final SomeArgs args = SomeArgs.obtain();
args.arg1 = Region.obtain(mMagnificationRegion);
@@ -1140,420 +1046,11 @@ final class AccessibilityController {
outSize.set(bounds.width(), bounds.height());
}
- void dump(PrintWriter pw, String prefix) {
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.dump(pw, prefix);
- }
- }
-
- private final class MagnifiedViewport {
-
- private final float mBorderWidth;
- private final int mHalfBorderWidth;
- private final int mDrawBorderInset;
-
- @Nullable private final ViewportWindow mWindow;
-
- private boolean mFullRedrawNeeded;
-
- MagnifiedViewport() {
- mBorderWidth = mDisplayContext.getResources().getDimension(
- com.android.internal.R.dimen.accessibility_magnification_indicator_width);
- mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
- mDrawBorderInset = (int) mBorderWidth / 2;
- mWindow = new ViewportWindow(mDisplayContext);
- }
-
- void updateBorderDrawingStatus(int screenWidth, int screenHeight) {
- mWindow.setBounds(mMagnificationRegion);
- final Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset,
- screenHeight - mDrawBorderInset);
- mWindow.invalidate(dirtyRect);
- } else {
- final Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(mMagnificationRegion);
- dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
- }
- }
-
- void setShowMagnifiedBorderIfNeeded() {
- // If this message is pending, we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShown(
- isFullscreenMagnificationActivated(), true);
- }
- }
-
- // Can be called outside of a surface transaction
- void showMagnificationBoundsIfNeeded() {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
- FLAGS_MAGNIFICATION_CALLBACK);
- }
- mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
- .sendToTarget();
- }
-
- void intersectWithDrawBorderInset(int screenWidth, int screenHeight) {
- mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
- Region.Op.INTERSECT);
- }
-
- void onDisplaySizeChanged() {
- // If fullscreen magnification is activated, hide the border immediately so
- // the user does not see strange artifacts during display size changed caused by
- // rotation or folding/unfolding the device. In the rotation case, the
- // screenshot used for rotation already has the border. After the rotation is
- // completed we will show the border.
- if (isFullscreenMagnificationActivated()) {
- setMagnifiedRegionBorderShown(false, false);
- final long delay = (long) (mLongAnimationDuration
- * mService.getWindowAnimationScaleLocked());
- Message message = mHandler.obtainMessage(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
- mHandler.sendMessageDelayed(message, delay);
- }
- mWindow.updateSize();
- }
-
- void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
- if (mWindow.setShown(shown, animate)) {
- mFullRedrawNeeded = true;
- // Clear the old region, so recomputeBounds will refresh the current region.
- mOldMagnificationRegion.set(0, 0, 0, 0);
- }
- }
-
- void drawWindowIfNeeded() {
- mWindow.postDrawIfNeeded();
- }
-
- void destroyWindow() {
- mWindow.releaseSurface();
- }
-
- void dump(PrintWriter pw, String prefix) {
- mWindow.dump(pw, prefix);
- }
-
- // TODO(291891390): Remove this class when we clean up the flag
- // alwaysDrawMagnificationFullscreenBorder
- private final class ViewportWindow implements Runnable {
- private static final String SURFACE_TITLE = "Magnification Overlay";
-
- private final Region mBounds = new Region();
- private final Rect mDirtyRect = new Rect();
- private final Paint mPaint = new Paint();
-
- private final SurfaceControl mSurfaceControl;
- /** After initialization, it should only be accessed from animation thread. */
- private final SurfaceControl.Transaction mTransaction;
- private final BLASTBufferQueue mBlastBufferQueue;
- private final Surface mSurface;
-
- private final AnimationController mAnimationController;
-
- private boolean mShown;
- private boolean mLastSurfaceShown;
- private int mAlpha;
- private int mPreviousAlpha;
-
- private volatile boolean mInvalidated;
-
- ViewportWindow(Context context) {
- SurfaceControl surfaceControl = null;
- try {
- surfaceControl = mDisplayContent
- .makeOverlay()
- .setName(SURFACE_TITLE)
- .setBLASTLayer()
- .setFormat(PixelFormat.TRANSLUCENT)
- .setCallsite("ViewportWindow")
- .build();
- } catch (OutOfResourcesException oore) {
- /* ignore */
- }
- mSurfaceControl = surfaceControl;
- mDisplay.getRealSize(mScreenSize);
- mBlastBufferQueue = new BLASTBufferQueue(SURFACE_TITLE, mSurfaceControl,
- mScreenSize.x, mScreenSize.y, PixelFormat.RGBA_8888);
-
- final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
- final int layer =
- mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) *
- WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
- t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0);
- InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
- mDisplayContent.getDisplayId(), "Magnification Overlay");
- t.apply();
- mTransaction = t;
- mSurface = mBlastBufferQueue.createSurface();
-
- mAnimationController = new AnimationController(context,
- mService.mH.getLooper());
-
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
- typedValue, true);
- final int borderColor = context.getColor(typedValue.resourceId);
-
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(mBorderWidth);
- mPaint.setColor(borderColor);
-
- mInvalidated = true;
- }
-
- /** Returns {@code true} if the state is changed to shown. */
- boolean setShown(boolean shown, boolean animate) {
- synchronized (mService.mGlobalLock) {
- if (mShown == shown) {
- return false;
- }
- mShown = shown;
- mAnimationController.onFrameShownStateChanged(shown, animate);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
- }
- }
- return shown;
- }
-
- @SuppressWarnings("unused")
- // Called reflectively from an animator.
- int getAlpha() {
- synchronized (mService.mGlobalLock) {
- return mAlpha;
- }
- }
-
- void setAlpha(int alpha) {
- synchronized (mService.mGlobalLock) {
- if (mAlpha == alpha) {
- return;
- }
- mAlpha = alpha;
- invalidate(null);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
- }
- }
- }
-
- void setBounds(Region bounds) {
- synchronized (mService.mGlobalLock) {
- if (mBounds.equals(bounds)) {
- return;
- }
- mBounds.set(bounds);
- invalidate(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
- }
- }
- }
-
- void updateSize() {
- synchronized (mService.mGlobalLock) {
- getDisplaySizeLocked(mScreenSize);
- mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y,
- PixelFormat.RGBA_8888);
- invalidate(mDirtyRect);
- }
- }
-
- void invalidate(Rect dirtyRect) {
- if (dirtyRect != null) {
- mDirtyRect.set(dirtyRect);
- } else {
- mDirtyRect.setEmpty();
- }
- mInvalidated = true;
- mService.scheduleAnimationLocked();
- }
-
- void postDrawIfNeeded() {
- if (mInvalidated) {
- mService.mAnimationHandler.post(this);
- }
- }
-
- @Override
- public void run() {
- drawOrRemoveIfNeeded();
- }
-
- /**
- * This method must only be called by animation handler directly to make sure
- * thread safe and there is no lock held outside.
- */
- private void drawOrRemoveIfNeeded() {
- // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
- // using WindowManagerGlobalLock. Grab copies of these values before
- // drawing on the canvas so that drawing can be performed outside of the lock.
- int alpha;
- boolean redrawBounds;
- Rect drawingRect = null;
- Region drawingBounds = null;
- synchronized (mService.mGlobalLock) {
- if (mBlastBufferQueue.mNativeObject == 0) {
- // Complete removal since releaseSurface has been called.
- if (mSurface.isValid()) {
- mTransaction.remove(mSurfaceControl).apply();
- mSurface.release();
- }
- return;
- }
- if (!mInvalidated) {
- return;
- }
- mInvalidated = false;
-
- alpha = mAlpha;
- // For b/325863281, we should ensure the drawn border path is cleared when
- // alpha = 0. Therefore, we cache the last used alpha when drawing as
- // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
- // the border is showing now, then we should still redraw the clear path
- // on the canvas so the border is cleared.
- redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
- if (redrawBounds) {
- drawingBounds = new Region(mBounds);
- // Empty dirty rectangle means unspecified.
- if (mDirtyRect.isEmpty()) {
- mBounds.getBounds(mDirtyRect);
- }
- mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
- drawingRect = new Rect(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
- Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
- }
- }
- }
-
- final boolean showSurface;
- // Draw without holding WindowManagerGlobalLock.
- if (redrawBounds) {
- Canvas canvas = null;
- try {
- canvas = mSurface.lockCanvas(drawingRect);
- } catch (IllegalArgumentException | OutOfResourcesException e) {
- /* ignore */
- }
- if (canvas == null) {
- return;
- }
- canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
- mPaint.setAlpha(alpha);
- canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
- mSurface.unlockCanvasAndPost(canvas);
- mPreviousAlpha = alpha;
- }
-
- showSurface = alpha > 0;
-
- if (showSurface && !mLastSurfaceShown) {
- mTransaction.show(mSurfaceControl).apply();
- mLastSurfaceShown = true;
- } else if (!showSurface && mLastSurfaceShown) {
- mTransaction.hide(mSurfaceControl).apply();
- mLastSurfaceShown = false;
- }
- }
-
- @GuardedBy("mService.mGlobalLock")
- void releaseSurface() {
- mBlastBufferQueue.destroy();
- // Post to perform cleanup on the thread which handles mSurface.
- mService.mAnimationHandler.post(this);
- }
-
- void dump(PrintWriter pw, String prefix) {
- pw.println(prefix
- + " mBounds= " + mBounds
- + " mDirtyRect= " + mDirtyRect
- + " mWidth= " + mScreenSize.x
- + " mHeight= " + mScreenSize.y);
- }
-
- private final class AnimationController extends Handler {
- private static final String PROPERTY_NAME_ALPHA = "alpha";
-
- private static final int MIN_ALPHA = 0;
- private static final int MAX_ALPHA = 255;
-
- private static final int MSG_FRAME_SHOWN_STATE_CHANGED = 1;
-
- private final ValueAnimator mShowHideFrameAnimator;
-
- AnimationController(Context context, Looper looper) {
- super(looper);
- mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
- PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
-
- Interpolator interpolator = new DecelerateInterpolator(2.5f);
- final long longAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_longAnimTime);
-
- mShowHideFrameAnimator.setInterpolator(interpolator);
- mShowHideFrameAnimator.setDuration(longAnimationDuration);
- }
-
- void onFrameShownStateChanged(boolean shown, boolean animate) {
- obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
- shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_FRAME_SHOWN_STATE_CHANGED: {
- final boolean shown = message.arg1 == 1;
- final boolean animate = message.arg2 == 1;
-
- if (animate) {
- if (mShowHideFrameAnimator.isRunning()) {
- mShowHideFrameAnimator.reverse();
- } else {
- if (shown) {
- mShowHideFrameAnimator.start();
- } else {
- mShowHideFrameAnimator.reverse();
- }
- }
- } else {
- mShowHideFrameAnimator.cancel();
- if (shown) {
- setAlpha(MAX_ALPHA);
- } else {
- setAlpha(MIN_ALPHA);
- }
- }
- } break;
- }
- }
- }
- }
- }
-
private class MyHandler extends Handler {
public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
-
- // TODO(291891390): Remove this field when we clean up the flag
- // alwaysDrawMagnificationFullscreenBorder
- public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
- public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
+ public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 5;
MyHandler(Looper looper) {
super(looper);
@@ -1577,17 +1074,6 @@ final class AccessibilityController {
mCallbacks.onDisplaySizeChanged();
} break;
- case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
- synchronized (mService.mGlobalLock) {
- if (isFullscreenMagnificationActivated()) {
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.setMagnifiedRegionBorderShown(true, true);
- }
- mService.scheduleAnimationLocked();
- }
- }
- } break;
-
case MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED: {
final boolean shown = message.arg1 == 1;
mCallbacks.onImeWindowVisibilityChanged(shown);
@@ -1784,22 +1270,13 @@ final class AccessibilityController {
mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
mDisplayId, visibleWindows);
- if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
- windows = buildWindowInfoListLocked(visibleWindows, screenSize);
- }
-
// Gets the top focused display Id and window token for supporting multi-display.
topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId();
topFocusedWindowToken = topFocusedWindowState.mClient.asBinder();
}
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
- mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
- topFocusedWindowToken, screenSize, visibleWindows);
- } else {
- mCallback.onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
- topFocusedWindowToken, windows);
- }
+ mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
+ topFocusedWindowToken, screenSize, visibleWindows);
// Recycle the windows as we do not need them.
for (final AccessibilityWindowsPopulator.AccessibilityWindow window : visibleWindows) {
@@ -1808,166 +1285,6 @@ final class AccessibilityController {
mInitialized = true;
}
- // Here are old code paths, called when computeWindowChangesOnA11yV2 flag is disabled.
- // LINT.IfChange
-
- /**
- * From a list of windows, decides windows to be exposed to accessibility based on touchable
- * region in the screen.
- */
- private List<WindowInfo> buildWindowInfoListLocked(List<AccessibilityWindow> visibleWindows,
- Point screenSize) {
- final List<WindowInfo> windows = new ArrayList<>();
- final Set<IBinder> addedWindows = mTempBinderSet;
- addedWindows.clear();
-
- boolean focusedWindowAdded = false;
-
- final int visibleWindowCount = visibleWindows.size();
-
- Region unaccountedSpace = mTempRegion;
- unaccountedSpace.set(0, 0, screenSize.x, screenSize.y);
-
- // Iterate until we figure out what is touchable for the entire screen.
- for (int i = 0; i < visibleWindowCount; i++) {
- final AccessibilityWindow a11yWindow = visibleWindows.get(i);
- final Region regionInWindow = new Region();
- a11yWindow.getTouchableRegionInWindow(regionInWindow);
- if (windowMattersToAccessibility(a11yWindow, regionInWindow, unaccountedSpace)) {
- addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
- if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
- updateUnaccountedSpace(a11yWindow, unaccountedSpace);
- }
- focusedWindowAdded |= a11yWindow.isFocused();
- } else if (a11yWindow.isUntouchableNavigationBar()) {
- // If this widow is navigation bar without touchable region, accounting the
- // region of navigation bar inset because all touch events from this region
- // would be received by launcher, i.e. this region is a un-touchable one
- // for the application.
- unaccountedSpace.op(
- getSystemBarInsetsFrame(
- mService.mWindowMap.get(a11yWindow.getWindowInfo().token)),
- unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
- }
-
- if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
- break;
- }
- }
-
- // Remove child/parent references to windows that were not added.
- final int windowCount = windows.size();
- for (int i = 0; i < windowCount; i++) {
- WindowInfo window = windows.get(i);
- if (!addedWindows.contains(window.parentToken)) {
- window.parentToken = null;
- }
- if (window.childTokens != null) {
- final int childTokenCount = window.childTokens.size();
- for (int j = childTokenCount - 1; j >= 0; j--) {
- if (!addedWindows.contains(window.childTokens.get(j))) {
- window.childTokens.remove(j);
- }
- }
- // Leave the child token list if empty.
- }
- }
-
- addedWindows.clear();
-
- return windows;
- }
-
- // Some windows should be excluded from unaccounted space computation, though they still
- // should be reported
- private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
- // Do not account space of trusted non-touchable windows, except the split-screen
- // divider.
- // If it's not trusted, touch events are not sent to the windows behind it.
- if (!a11yWindow.isTouchable()
- && (a11yWindow.getType() != TYPE_DOCK_DIVIDER)
- && a11yWindow.isTrustedOverlay()) {
- return false;
- }
-
- if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
- return false;
- }
- return true;
- }
-
- private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
- Region regionInScreen, Region unaccountedSpace) {
- if (a11yWindow.isFocused()) {
- return true;
- }
-
- // Ignore non-touchable windows, except the split-screen divider, which is
- // occasionally non-touchable but still useful for identifying split-screen
- // mode and the PIP menu.
- if (!a11yWindow.isTouchable()
- && (a11yWindow.getType() != TYPE_DOCK_DIVIDER
- && !a11yWindow.isPIPMenu())) {
- return false;
- }
-
- // If the window is completely covered by other windows - ignore.
- if (unaccountedSpace.quickReject(regionInScreen)) {
- return false;
- }
-
- // Add windows of certain types not covered by modal windows.
- if (isReportedWindowType(a11yWindow.getType())) {
- return true;
- }
-
- return false;
- }
-
- private void updateUnaccountedSpace(AccessibilityWindow a11yWindow,
- Region unaccountedSpace) {
- if (a11yWindow.getType()
- != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
- // Account for the space this window takes if the window
- // is not an accessibility overlay which does not change
- // the reported windows.
- final Region touchableRegion = mTempRegion2;
- a11yWindow.getTouchableRegionInScreen(touchableRegion);
- unaccountedSpace.op(touchableRegion, unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
- }
- }
-
- private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow,
- Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) {
- final WindowInfo window = a11yWindow.getWindowInfo();
- if (window.token == null) {
- // The window was used in calculating visible windows but does not have an
- // associated IWindow token, so exclude it from the list returned to accessibility.
- return;
- }
- window.regionInScreen.set(regionInScreen);
- window.layer = tokenOut.size();
- out.add(window);
- tokenOut.add(window.token);
- }
-
- private static boolean isReportedWindowType(int windowType) {
- return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
- && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
- && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_DRAG
- && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
- && windowType != WindowManager.LayoutParams.TYPE_POINTER
- && windowType != TYPE_MAGNIFICATION_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
- }
-
- // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java)
-
private WindowState getTopFocusWindow() {
return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index fd2a909f8b05..3c0e0582e8a8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,11 +150,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
- onWindowInfosChangedInternal(windowHandles, displayInfos);
- } else {
- mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
- }
+ onWindowInfosChangedInternal(windowHandles, displayInfos);
}
private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
@@ -724,8 +720,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
}
// Compute system bar insets frame if needed.
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()
- && windowState != null && instance.isUntouchableNavigationBar()) {
+ if (windowState != null && instance.isUntouchableNavigationBar()) {
final InsetsSourceProvider provider =
windowState.getControllableInsetProvider();
if (provider != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c1e859ddcccf..05794cd61fb3 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -42,8 +42,8 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IMMERSIVE;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -500,6 +500,8 @@ class ActivityClientController extends IActivityClientController.Stub {
r.app.setLastActivityFinishTimeIfNeeded(SystemClock.uptimeMillis());
}
+ mService.mAmInternal.addCreatorToken(resultData, r.packageName);
+
final long origId = Binder.clearCallingIdentity();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
try {
@@ -884,7 +886,10 @@ class ActivityClientController extends IActivityClientController.Stub {
@Override
public boolean convertToTranslucent(IBinder token, Bundle options) {
- final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options);
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ options, callingPid, callingUid);
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index fb5c1154c7f0..3f814f9db1c8 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -244,7 +244,7 @@ class ActivityMetricsLogger {
status = ":completed-same-process:";
} else {
if (endInfo.mTransitionType == TYPE_TRANSITION_HOT_LAUNCH) {
- status = ":completed-hot:";
+ status = !endInfo.mRelaunched ? ":completed-hot:" : ":completed-relaunch:";
} else if (endInfo.mTransitionType == TYPE_TRANSITION_WARM_LAUNCH) {
status = ":completed-warm:";
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5d4c42605e1d..496e64f5f7a1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -33,7 +33,6 @@ import static android.app.ActivityOptions.ANIM_UNDEFINED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.WaitResult.INVALID_DELAY;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
@@ -42,7 +41,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -127,19 +125,19 @@ import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTAINERS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTAINERS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -230,6 +228,7 @@ import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -255,6 +254,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -629,6 +629,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The locusId associated with this activity, if set.
private LocusId mLocusId;
+ // The timestamp of the last request to show the "Open in browser" education
+ public long mRequestOpenInBrowserEducationTimestamp;
+
// Whether the activity was launched from a bubble.
private boolean mLaunchedFromBubble;
@@ -704,9 +707,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private boolean mOccludesParent;
- /** Whether the activity have style floating */
- private boolean mStyleFloating;
-
/**
* Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute
* from the style of activity. Because we don't want {@link WindowContainer#getOrientation()}
@@ -788,10 +788,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
private boolean mIsEligibleForFixedOrientationLetterbox;
- // activity is not displayed?
- // TODO: rename to mNoDisplay
- @VisibleForTesting
- boolean noDisplay;
+ /**
+ * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+ */
+ private boolean mNoDisplay;
final boolean mShowForAllUsers;
// TODO: Make this final
int mTargetSdk;
@@ -870,8 +870,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
})
@interface SplashScreenBehavior { }
- // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
- boolean mIsExiting;
// Force an app transition to be ran in the case the visibility of the app did not change.
// We use this for the case of moving a Root Task to the back with multiple activities, and the
// top activity enters PIP; the bottom activity's visibility stays the same, but we need to
@@ -1175,7 +1173,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pw.print(" inHistory="); pw.print(inHistory);
pw.print(" idle="); pw.println(idle);
pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent());
- pw.print(" noDisplay="); pw.print(noDisplay);
+ pw.print(" mNoDisplay="); pw.print(mNoDisplay);
pw.print(" immersive="); pw.print(immersive);
pw.print(" launchMode="); pw.println(launchMode);
pw.print(prefix); pw.print("mActivityType=");
@@ -1215,10 +1213,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pw.print(" lastAllDrawn="); pw.print(mLastAllDrawn);
pw.println(")");
}
- if (mStartingData != null || firstWindowDrawn || mIsExiting) {
+ if (mStartingData != null || firstWindowDrawn) {
pw.print(prefix); pw.print("startingData="); pw.print(mStartingData);
- pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
- pw.print(" mIsExiting="); pw.println(mIsExiting);
+ pw.print(" firstWindowDrawn="); pw.println(firstWindowDrawn);
}
if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
|| startingMoved || mVisibleSetFromTransferredStartingWindow) {
@@ -1440,7 +1437,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
boolean scheduleTopResumedActivityChanged(boolean onTop) {
- if (!attachedToProcess()) {
+ if (!attachedToProcess() || isState(DESTROYING, DESTROYED)) {
ProtoLog.w(WM_DEBUG_STATES,
"Can't report activity position update - client not running, "
+ "activityRecord=%s", this);
@@ -1623,6 +1620,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newParent.setResumedActivity(this, "onParentChanged");
}
mAppCompatController.getTransparentPolicy().start();
+ if (mState == INITIALIZING && isRestrictedFixedOrientation(info.screenOrientation)) {
+ Slog.i(TAG, "Ignoring manifest-declared fixed orientation "
+ + ActivityInfo.screenOrientationToString(info.screenOrientation)
+ + " of " + this + " since target sdk 36");
+ }
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -1793,8 +1795,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
prevDc.onRunningActivityChanged();
- // TODO(b/169035022): move to a more-appropriate place.
- mTransitionController.collect(this);
if (prevDc.mOpeningApps.remove(this)) {
// Transfer opening transition to new display.
mDisplayContent.mOpeningApps.add(this);
@@ -2011,20 +2011,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (ent != null) {
final boolean styleTranslucent = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- mStyleFloating = ent.array.getBoolean(
+ final boolean styleFloating = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false);
- mOccludesParent = !(styleTranslucent || mStyleFloating)
+ mOccludesParent = !(styleTranslucent || styleFloating)
// This style is propagated to the main window attributes with
// FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
|| ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
mStyleFillsParent = mOccludesParent;
- noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
mOptOutEdgeToEdge = ent.array.getBoolean(
R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
} else {
- mStyleFloating = false;
mStyleFillsParent = mOccludesParent = true;
- noDisplay = false;
+ mNoDisplay = false;
mOptOutEdgeToEdge = false;
}
@@ -2151,7 +2150,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
- mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord);
+ final boolean appOptInTouchPassThrough =
+ options != null && options.isAllowPassThroughOnTouchOutside();
+ mActivityRecordInputSink = new ActivityRecordInputSink(
+ this, sourceRecord, appOptInTouchPassThrough);
mAppActivityEmbeddingSplitsEnabled = isAppActivityEmbeddingSplitsEnabled();
mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
@@ -2538,6 +2540,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
if (!activityAllDrawn && !isActivityHome) {
+ // Only check the special case of a fragment host task because the starting window
+ // may not be visible if the client organizer delays the transition ready.
+ if (task.mTaskFragmentHostProcessName != null) {
+ // It may be launched from a task trampoline that already has a starting window.
+ // Return NONE because 2 consecutive splashes may not look smooth in visual.
+ final Task prevTask = task.getParent().getTaskBelow(task);
+ if (prevTask != null) {
+ final ActivityRecord prevTaskTop = prevTask.getTopMostActivity();
+ if (prevTaskTop != null && prevTaskTop.hasStartingWindow()) {
+ return STARTING_WINDOW_TYPE_NONE;
+ }
+ }
+ }
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
}
@@ -2629,8 +2644,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
|| mStartingWindow == null
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
- // skip copy splash screen to client if it was resized
- || (mStartingData != null && mStartingData.mResizedFromTransfer)
+ // Skip copy splash screen to client if it was resized, or the starting data already
+ // requested to be removed after transaction commit.
+ || (mStartingData != null && (mStartingData.mResizedFromTransfer
+ || mStartingData.mRemoveAfterTransaction != AFTER_TRANSACTION_IDLE))
|| isRelaunching()) {
return false;
}
@@ -3086,8 +3103,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return occludesParent(true /* includingFinishing */);
}
- boolean isStyleFloating() {
- return mStyleFloating;
+ boolean isNoDisplay() {
+ return mNoDisplay;
+ }
+
+ /**
+ * Exposed only for testing and should not be used to modify value of {@link #mNoDisplay}.
+ */
+ @VisibleForTesting
+ void setIsNoDisplay(boolean isNoDisplay) {
+ mNoDisplay = isNoDisplay;
}
/** Returns true if this activity is not finishing, is opaque and fills the entire space of
@@ -3168,14 +3193,69 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
}
- boolean isResizeable() {
- return isResizeable(/* checkPictureInPictureSupport */ true);
+ /**
+ * Returns {@code true} if the orientation will be ignored for {@link #isUniversalResizeable()}.
+ */
+ private boolean isRestrictedFixedOrientation(
+ @ActivityInfo.ScreenOrientation int orientation) {
+ // Exclude "locked" because it is not explicit portrait or landscape.
+ return orientation != ActivityInfo.SCREEN_ORIENTATION_LOCKED
+ && ActivityInfo.isFixedOrientation(orientation)
+ && isUniversalResizeable();
}
- boolean isResizeable(boolean checkPictureInPictureSupport) {
+ /**
+ * Returns {@code true} if the fixed orientation, aspect ratio, resizability of this activity
+ * will be ignored.
+ */
+ boolean isUniversalResizeable() {
+ final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.getConfiguration()
+ .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ && mDisplayContent.getIgnoreOrientationRequest();
+ if (!canBeUniversalResizeable(info.applicationInfo, mWmService, isLargeScreen,
+ true /* forActivity */)) {
+ return false;
+ }
+ if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
+ return false;
+ }
+ // If the user preference respects aspect ratio, then it becomes non-resizable.
+ return mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .userPreferenceCompatibleWithNonResizability();
+ }
+
+ /**
+ * Returns {@code true} if the fixed orientation, aspect ratio, resizability of the application
+ * can be ignored.
+ */
+ static boolean canBeUniversalResizeable(ApplicationInfo appInfo, WindowManagerService wms,
+ boolean isLargeScreen, boolean forActivity) {
+ if (appInfo.category == ApplicationInfo.CATEGORY_GAME) {
+ return false;
+ }
+ final boolean compatEnabled = isLargeScreen && Flags.universalResizableByDefault()
+ && appInfo.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ final boolean configEnabled = (isLargeScreen
+ ? wms.mConstants.mIgnoreActivityOrientationRequestLargeScreen
+ : wms.mConstants.mIgnoreActivityOrientationRequestSmallScreen)
+ && !wms.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(
+ appInfo.packageName);
+ if (!compatEnabled && !configEnabled) {
+ return false;
+ }
+ if (forActivity) {
+ // The caller will check both application and activity level property.
+ return true;
+ }
+ return !AppCompatController.allowRestrictedResizability(wms.mContext.getPackageManager(),
+ appInfo.packageName);
+ }
+
+ boolean isResizeable() {
return mAtmService.mForceResizableActivities
|| ActivityInfo.isResizeableMode(info.resizeMode)
- || (info.supportsPictureInPicture() && checkPictureInPictureSupport)
+ || info.supportsPictureInPicture()
+ || isUniversalResizeable()
// If the activity can be embedded, it should inherit the bounds of task fragment.
|| isEmbedded();
}
@@ -3604,16 +3684,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pauseKeyDispatchingLocked();
- // We are finishing the top focused activity and its task has nothing to be focused so
- // the next focusable task should be focused.
- if (mayAdjustTop && task.topRunningActivity(true /* focusableOnly */)
- == null) {
- task.adjustFocusToNextFocusableTask("finish-top", false /* allowFocusSelf */,
- shouldAdjustGlobalFocus);
- }
-
- finishActivityResults(resultCode, resultData, resultGrants);
-
final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
final WindowContainer<?> trigger = endTask ? task : this;
@@ -3624,6 +3694,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (transition != null) {
transition.collectClose(trigger);
}
+ // We are finishing the top focused activity and its task has nothing to be focused so
+ // the next focusable task should be focused.
+ if (mayAdjustTop && task.topRunningActivity(true /* focusableOnly */)
+ == null) {
+ task.adjustFocusToNextFocusableTask("finish-top", false /* allowFocusSelf */,
+ shouldAdjustGlobalFocus);
+ }
+
+ finishActivityResults(resultCode, resultData, resultGrants);
+
if (isState(RESUMED)) {
if (endTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -4335,21 +4415,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
super.removeImmediately();
}
- @Override
- void removeIfPossible() {
- mIsExiting = false;
- removeAllWindowsIfPossible();
- removeImmediately();
- }
-
- @Override
- boolean handleCompleteDeferredRemoval() {
- if (mIsExiting) {
- removeIfPossible();
- }
- return super.handleCompleteDeferredRemoval();
- }
-
void onRemovedFromDisplay() {
if (mRemovingFromDisplay) {
return;
@@ -4527,12 +4592,35 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// at #postWindowRemoveCleanupLocked
return false;
}
+
+ // Link the fixed rotation transform to this activity since we are transferring the
+ // starting window.
+ if (fromActivity.hasFixedRotationTransform()) {
+ mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(this,
+ false /* checkOpening */);
+ }
// Do not transfer if the orientation doesn't match, redraw starting window while it is
// on top will cause flicker.
- if (fromActivity.getRequestedConfigurationOrientation()
- != getRequestedConfigurationOrientation()) {
+ final int fromOrientation = fromActivity.getConfiguration().orientation;
+ final int requestedOrientation = getRequestedConfigurationOrientation();
+ if (requestedOrientation == ORIENTATION_UNDEFINED) {
+ if (fromOrientation != getConfiguration().orientation) {
+ return false;
+ }
+ } else if (fromOrientation != requestedOrientation) {
return false;
}
+
+ // If another activity above the activity which has starting window, allows to steal the
+ // starting window if the above activity isn't drawn.
+ if (task.getChildCount() >= 3
+ && fromActivity.mStartingData.mAssociatedTask == null) {
+ final ActivityRecord aboveFrom = task.getActivityAbove(fromActivity);
+ if (aboveFrom != null && aboveFrom != this && !aboveFrom.mReportedDrawn) {
+ return false;
+ }
+ }
+
// In this case, the starting icon has already been displayed, so start
// letting windows get shown immediately without any more transitions.
if (fromActivity.mVisible) {
@@ -4544,13 +4632,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final long origId = Binder.clearCallingIdentity();
try {
- // Link the fixed rotation transform to this activity since we are transferring the
- // starting window.
- if (fromActivity.hasFixedRotationTransform()) {
- mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(this,
- false /* checkOpening */);
- }
-
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
mStartingSurface = fromActivity.mStartingSurface;
@@ -4563,6 +4644,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
tStartingWindow.mToken = this;
tStartingWindow.mActivityRecord = this;
+ if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+ // The removal of starting window should wait for window drawn of current
+ // activity.
+ final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
+ if (mainWin == null || !mainWin.isDrawn()) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+ mStartingData.mPrepareRemoveAnimation = false;
+ }
+ }
+
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"Removing starting %s from %s", tStartingWindow, fromActivity);
mTransitionController.collect(tStartingWindow);
@@ -4906,7 +4997,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult();
try {
if (lifecycleItem != null) {
- mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+ mAtmService.getLifecycleManager().scheduleTransactionItems(
app.getThread(), activityResultItem, lifecycleItem);
} else {
Slog.w(TAG, "Unable to get the lifecycle item for state " + mState
@@ -5498,7 +5589,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
clearAllDrawn();
// Reset the draw state in order to prevent the starting window to be immediately
// dismissed when the app still has the surface.
- if (!isVisible() && !isClientVisible()) {
+ if (!Flags.resetDrawStateOnClientInvisible()
+ && !isVisible() && !isClientVisible()) {
forAllWindows(w -> {
if (w.mWinAnimator.mDrawState == HAS_DRAWN) {
w.mWinAnimator.resetDrawState();
@@ -5530,8 +5622,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!visible) {
if (mTransitionController.inPlayingTransition(this)) {
mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
- } else if (mTransitionController.inFinishingTransition(this)) {
- mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+ if (mTransitionController.mFinishingTransition != null
+ && mTransitionController.mFinishingTransition.isTransientLaunch(this)) {
+ mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+ }
}
} else {
mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED;
@@ -6057,7 +6151,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
// No display activities never add a window, so there is no point in waiting them for
// relayout.
- if (noDisplay || !isKeyguardLocked()) {
+ if (mNoDisplay || !isKeyguardLocked()) {
return;
}
@@ -6403,7 +6497,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// and the token could be null.
return;
}
- r.mDisplayContent.mAppCompatCameraPolicy.onActivityRefreshed(r);
+ AppCompatCameraPolicy.onActivityRefreshed(r);
}
static void splashScreenAttachedLocked(IBinder token) {
@@ -6450,7 +6544,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
returningOptions = null;
if (canTurnScreenOn()) {
- mTaskSupervisor.wakeUp("turnScreenOnFlag");
+ mTaskSupervisor.wakeUp(getDisplayId(), "turnScreenOnFlag");
} else {
// If the screen is going to turn on because the caller explicitly requested it and
// the keyguard is not showing don't attempt to sleep. Otherwise the Activity will
@@ -7291,6 +7385,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return mLocusId;
}
+ void requestOpenInBrowserEducation() {
+ mRequestOpenInBrowserEducationTimestamp = System.currentTimeMillis();
+ final Task task = getTask();
+ if (task != null) {
+ final boolean force = isVisibleRequested() && this == task.getTopNonFinishingActivity();
+ getTask().dispatchTaskInfoChangedIfNeeded(force);
+ }
+ }
+
public void reportScreenCaptured() {
if (mCaptureCallbacks != null) {
final int n = mCaptureCallbacks.beginBroadcast();
@@ -8078,7 +8181,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Setting requested orientation %s for %s",
ActivityInfo.screenOrientationToString(requestedOrientation), this);
- setOrientation(requestedOrientation, this);
+ final int resolvedOrientation = setOrientation(requestedOrientation, this);
+ if (resolvedOrientation != requestedOrientation
+ && isRestrictedFixedOrientation(requestedOrientation)) {
+ Slog.i(TAG, "Ignoring requested fixed orientation "
+ + ActivityInfo.screenOrientationToString(requestedOrientation)
+ + " of " + this + " since target sdk 36");
+ }
// Push the new configuration to the requested app in case where it's not pushed, e.g. when
// the request is handled at task level with letterbox.
@@ -8168,11 +8277,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
- final int candidateOrientation;
- if (!mWmService.mConstants.mIgnoreActivityOrientationRequest
- || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
- candidateOrientation = super.getOverrideOrientation();
- } else {
+ int candidateOrientation = super.getOverrideOrientation();
+ if (isRestrictedFixedOrientation(candidateOrientation)) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
@@ -8377,15 +8483,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// and back which can cause visible issues (see b/184078928).
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
- final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM
- && mAppCompatController.getAppCompatCameraOverrides().getFreeformCameraCompatMode()
- != CAMERA_COMPAT_FREEFORM_NONE;
// Bubble activities should always fill their parent and should not be letterboxed.
final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
&& (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN
- || isInCameraCompatFreeform
+ || AppCompatCameraPolicy.shouldCameraCompatControlOrientation(this)
// When starting to switch between PiP and fullscreen, the task is pinned
// and the activity is fullscreen. But only allow to apply letterbox if the
// activity is exiting PiP because an entered PiP should fill the task.
@@ -8868,6 +8971,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAppCompatController.getAppCompatSizeCompatModePolicy();
if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
+ && mAppCompatDisplayInsets != null
&& !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
@@ -9448,8 +9552,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!shouldBeResumed(/* activeActivity */ null)) {
return;
}
- mDisplayContent.mAppCompatCameraPolicy.onActivityConfigurationChanging(
- this, newConfig, lastReportedConfig);
+
+ AppCompatCameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
}
/** Get process configuration, or global config if the process is not set. */
@@ -9624,7 +9728,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else {
lifecycleItem = new PauseActivityItem(token);
}
- mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+ mAtmService.getLifecycleManager().scheduleTransactionItems(
app.getThread(), callbackItem, lifecycleItem);
startRelaunching();
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
@@ -10031,7 +10135,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
StringBuilder sb = new StringBuilder(128);
sb.append("ActivityRecord{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(System.identityHashCode(this));
sb.append(" u");
sb.append(mUserId);
sb.append(' ');
@@ -10225,7 +10329,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
.isVisibilityUnknown(this)) {
return false;
}
- if (!isVisibleRequested()) return true;
+ if (!isVisibleRequested()) {
+ // TODO(b/294925498): Remove this finishing check once we have accurate ready tracking.
+ if (task != null && task.getPausingActivity() == this) {
+ // Visibility of starting activities isn't calculated until pause-complete, so if
+ // this is not paused yet, don't consider it ready.
+ return false;
+ }
+ return true;
+ }
if (mPendingRelaunchCount > 0) return false;
// Wait for attach. That is the earliest time where we know if there will be an associated
// display rotation. If we don't wait, the starting-window can finishDrawing first and
@@ -10283,6 +10395,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) {
pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top);
}
+
+ if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+ PackageManager pm = mAtmService.mContext.getPackageManager();
+ if (pictureInPictureArgs.isAutoEnterEnabled()
+ && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, packageName)
+ == PackageManager.PERMISSION_DENIED) {
+ Log.i(TAG,
+ "Auto-enter PiP only allowed on TV if android.permission"
+ + ".TV_IMPLICIT_ENTER_PIP permission is held by the app.");
+ PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+ builder.setAutoEnterEnabled(false);
+ pictureInPictureArgs.copyOnlySet(builder.build());
+ }
+ }
}
private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 1a197875ba31..ea6506a03a80 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -16,13 +16,18 @@
package com.android.server.wm;
+import android.app.ActivityOptions;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
import android.os.InputConfig;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
/**
* Creates a InputWindowHandle that catches all touches that would otherwise pass through an
* Activity.
@@ -35,6 +40,20 @@ class ActivityRecordInputSink {
@ChangeId
static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
+ /**
+ * If the app's target SDK is 36+, pass-through touches from a cross-uid overlaying activity is
+ * blocked by default. The activity may opt in to receive pass-through touches using
+ * {@link ActivityOptions#setAllowPassThroughOnTouchOutside}, which allows the to-be-launched
+ * cross-uid overlaying activity and other activities in that app to pass through touches. The
+ * activity needs to ensure that it trusts the overlaying app and its content is not vulnerable
+ * to UI redressing attacks.
+ *
+ * @see ActivityOptions#setAllowPassThroughOnTouchOutside
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ static final long ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT = 358129114L;
+
private final ActivityRecord mActivityRecord;
private final boolean mIsCompatEnabled;
private final String mName;
@@ -42,13 +61,24 @@ class ActivityRecordInputSink {
private InputWindowHandleWrapper mInputWindowHandleWrapper;
private SurfaceControl mSurfaceControl;
- ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) {
+ ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord,
+ boolean appOptInTouchPassThrough) {
mActivityRecord = activityRecord;
mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
mActivityRecord.getUid());
mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink "
+ mActivityRecord.mActivityComponent.flattenToShortString();
- if (sourceRecord != null) {
+
+ if (sourceRecord == null) {
+ return;
+ }
+ // If the source activity has target sdk 36+, it is required to opt in to receive
+ // pass-through touches from the overlaying activity.
+ final boolean isTouchPassThroughOptInEnforced = CompatChanges.isChangeEnabled(
+ ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT,
+ sourceRecord.getUid());
+ if (!Flags.touchPassThroughOptIn() || !isTouchPassThroughOptInEnforced
+ || appOptInTouchPassThrough) {
sourceRecord.mAllowedTouchUid = mActivityRecord.getUid();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index dcc325eae702..ed8b6890b2d3 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import android.annotation.NonNull;
import android.app.servertransaction.RefreshCallbackItem;
@@ -89,7 +89,7 @@ class ActivityRefresher {
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
try {
- activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+ activity.mAtmService.getLifecycleManager().scheduleTransactionItems(
activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
mHandler.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 24ed1bbe0eb1..57a0bb53c9dc 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -18,6 +18,8 @@ package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static com.android.server.wm.SnapshotPersistQueue.MAX_STORE_QUEUE_DEPTH;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -343,6 +345,11 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
if (DEBUG) {
Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity);
}
+ if (mPersister.mSnapshotPersistQueue.peekWriteQueueSize() >= MAX_STORE_QUEUE_DEPTH
+ || mPersister.mSnapshotPersistQueue.peekQueueSize() > MAX_PERSIST_SNAPSHOT_COUNT) {
+ Slog.w(TAG, "Skipping recording activity snapshot, too many requests!");
+ return;
+ }
final int size = activity.size();
final int[] mixedCode = new int[size];
if (size == 1) {
@@ -432,7 +439,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
addBelowActivityIfExist(ar, mPendingLoadActivity, false, "load-snapshot");
} else {
// remove the snapshot for the one below close
- addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot");
+ addBelowActivityIfExist(ar, mPendingRemoveActivity, false, "remove-snapshot");
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 35ec5adf54b0..3f24da9d89f2 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
+import static com.android.server.wm.ActivityStarter.Request.DEFAULT_INTENT_CREATOR_UID;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
@@ -33,7 +34,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.BackgroundStartPrivileges;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -43,7 +43,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.Trace;
import android.os.UserHandle;
@@ -191,7 +190,8 @@ public class ActivityStartController {
.setOutActivity(tmpOutRecord)
.setCallingUid(0)
.setActivityInfo(aInfo)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
if (rootHomeTask.mInResumeTopActivity) {
@@ -280,8 +280,8 @@ public class ActivityStartController {
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
* null if not originated by PendingIntent
- * @param forcedBalByPiSender If set to allow, the
- * PendingIntent's sender will try to force allow background activity starts.
+ * @param allowBalExemptionForSystemProcess If set to {@code true}, the
+ * PendingIntent's sender will allow additional exemptions.
* This is only possible if the sender of the PendingIntent is a system process.
*/
final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
@@ -289,7 +289,7 @@ public class ActivityStartController {
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason,
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender) {
+ boolean allowBalExemptionForSystemProcess) {
userId = checkTargetUser(userId, validateIncomingUser, realCallingPid, realCallingUid,
reason);
@@ -310,7 +310,7 @@ public class ActivityStartController {
.setUserId(userId)
.setInTask(inTask)
.setOriginatingPendingIntent(originatingPendingIntent)
- .setBackgroundStartPrivileges(forcedBalByPiSender)
+ .setAllowBalExemptionForSystemProcess(allowBalExemptionForSystemProcess)
.execute();
}
@@ -325,18 +325,18 @@ public class ActivityStartController {
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
* null if not originated by PendingIntent
- * @param forcedBalByPiSender If set to allow, the
- * PendingIntent's sender will try to force allow background activity starts.
+ * @param allowBalExemptionForSystemProcess If set to {@code true}, the
+ * PendingIntent's sender will allow additional exemptions.
* This is only possible if the sender of the PendingIntent is a system process.
*/
final int startActivitiesInPackage(int uid, String callingPackage,
@Nullable String callingFeatureId, Intent[] intents, String[] resolvedTypes,
IBinder resultTo, SafeActivityOptions options, int userId, boolean validateIncomingUser,
PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender) {
+ boolean allowBalExemptionForSystemProcess) {
return startActivitiesInPackage(uid, 0 /* realCallingPid */, -1 /* realCallingUid */,
callingPackage, callingFeatureId, intents, resolvedTypes, resultTo, options, userId,
- validateIncomingUser, originatingPendingIntent, forcedBalByPiSender);
+ validateIncomingUser, originatingPendingIntent, allowBalExemptionForSystemProcess);
}
/**
@@ -351,15 +351,15 @@ public class ActivityStartController {
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
* null if not originated by PendingIntent
- * @param forcedBalByPiSender If set to allow, the
- * PendingIntent's sender will try to force allow background activity starts.
+ * @param allowBalExemptionForSystemProcess If set to {@code true}, the
+ * PendingIntent's sender will allow additional exemptions.
* This is only possible if the sender of the PendingIntent is a system process.
*/
final int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid,
String callingPackage, @Nullable String callingFeatureId, Intent[] intents,
String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender) {
+ boolean allowBalExemptionForSystemProcess) {
final String reason = "startActivityInPackage";
@@ -369,14 +369,14 @@ public class ActivityStartController {
// TODO: Switch to user app stacks here.
return startActivities(null, uid, realCallingPid, realCallingUid, callingPackage,
callingFeatureId, intents, resolvedTypes, resultTo, options, userId, reason,
- originatingPendingIntent, forcedBalByPiSender);
+ originatingPendingIntent, allowBalExemptionForSystemProcess);
}
int startActivities(IApplicationThread caller, int callingUid, int incomingRealCallingPid,
int incomingRealCallingUid, String callingPackage, @Nullable String callingFeatureId,
Intent[] intents, String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options,
int userId, String reason, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender) {
+ boolean allowBalExemptionForSystemProcess) {
if (intents == null) {
throw new NullPointerException("intents is null");
}
@@ -442,6 +442,17 @@ public class ActivityStartController {
0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid,
callingPid);
aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
+ int creatorUid = DEFAULT_INTENT_CREATOR_UID;
+ String creatorPackage = null;
+ if (ActivityManagerService.IntentCreatorToken.isValid(intent)) {
+ ActivityManagerService.IntentCreatorToken creatorToken =
+ (ActivityManagerService.IntentCreatorToken) intent.getCreatorToken();
+ if (creatorToken.getCreatorUid() != filterCallingUid) {
+ creatorUid = creatorToken.getCreatorUid();
+ creatorPackage = creatorToken.getCreatorPackage();
+ }
+ // leave creatorUid as -1 if the intent creator is the same as the launcher
+ }
if (aInfo != null) {
try {
@@ -455,6 +466,24 @@ public class ActivityStartController {
return START_CANCELED;
}
+ if (creatorUid != DEFAULT_INTENT_CREATOR_UID) {
+ try {
+ NeededUriGrants creatorIntentGrants = mSupervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, creatorUid,
+ aInfo.applicationInfo.packageName,
+ UserHandle.getUserId(aInfo.applicationInfo.uid));
+ if (intentGrants == null) {
+ intentGrants = creatorIntentGrants;
+ } else {
+ intentGrants.merge(creatorIntentGrants);
+ }
+ } catch (SecurityException securityException) {
+ ActivityStarter.logAndThrowExceptionForIntentRedirect(mService.mContext,
+ "Creator URI Grant Caused Exception.", intent, creatorUid,
+ creatorPackage, filterCallingUid, callingPackage,
+ securityException);
+ }
+ }
if ((aInfo.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
throw new IllegalArgumentException(
@@ -478,6 +507,8 @@ public class ActivityStartController {
.setCallingUid(callingUid)
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
+ .setIntentCreatorUid(creatorUid)
+ .setIntentCreatorPackage(creatorPackage)
.setRealCallingPid(realCallingPid)
.setRealCallingUid(realCallingUid)
.setActivityOptions(checkedOptions)
@@ -487,7 +518,7 @@ public class ActivityStartController {
// top one as otherwise an activity below might consume it.
.setAllowPendingRemoteAnimationRegistryLookup(top /* allowLookup*/)
.setOriginatingPendingIntent(originatingPendingIntent)
- .setBackgroundStartPrivileges(forcedBalByPiSender);
+ .setAllowBalExemptionForSystemProcess(allowBalExemptionForSystemProcess);
}
// Log if the activities to be started have different uids.
if (startingUidPkgs.size() > 1) {
@@ -550,14 +581,14 @@ public class ActivityStartController {
* Starts an activity in the TaskFragment.
* @param taskFragment TaskFragment {@link TaskFragment} to start the activity in.
* @param activityIntent intent to start the activity.
- * @param activityOptions ActivityOptions to start the activity with.
+ * @param activityOptions SafeActivityOptions to start the activity with.
* @param resultTo the caller activity
* @param callingUid the caller uid
* @param callingPid the caller pid
* @return the start result.
*/
int startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
- @NonNull Intent activityIntent, @Nullable Bundle activityOptions,
+ @NonNull Intent activityIntent, @Nullable SafeActivityOptions activityOptions,
@Nullable IBinder resultTo, int callingUid, int callingPid,
@Nullable IBinder errorCallbackToken) {
final ActivityRecord caller =
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f4fcf3c26415..2781592c6b4f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -27,6 +27,7 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER;
import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityManager.isStartResultSuccessful;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -54,14 +55,16 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static android.content.pm.ActivityInfo.launchModeToString;
import static android.os.Process.INVALID_UID;
+import static android.security.Flags.preventIntentRedirectAbortOrThrowException;
+import static android.security.Flags.preventIntentRedirectShowToast;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
@@ -86,6 +89,7 @@ import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENS
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
import android.annotation.IntDef;
@@ -93,15 +97,17 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.BackgroundStartPrivileges;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
+import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
@@ -125,12 +131,15 @@ import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
import android.util.Pools.SynchronizedPool;
import android.util.Slog;
+import android.widget.Toast;
import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.protolog.ProtoLog;
+import com.android.server.UiThread;
+import com.android.server.am.ActivityManagerService.IntentCreatorToken;
import com.android.server.am.PendingIntentRecord;
import com.android.server.pm.InstantAppResolver;
import com.android.server.pm.PackageArchiver;
@@ -186,6 +195,10 @@ class ActivityStarter {
@Disabled
static final long ASM_RESTRICTIONS = 230590090L;
+ @ChangeId
+ @Overridable
+ private static final long ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION = 29623414L;
+
private final ActivityTaskManagerService mService;
private final RootWindowContainer mRootWindowContainer;
private final ActivityTaskSupervisor mSupervisor;
@@ -383,6 +396,7 @@ class ActivityStarter {
private static final int DEFAULT_CALLING_PID = 0;
static final int DEFAULT_REAL_CALLING_UID = -1;
static final int DEFAULT_REAL_CALLING_PID = 0;
+ static final int DEFAULT_INTENT_CREATOR_UID = -1;
IApplicationThread caller;
Intent intent;
@@ -403,6 +417,8 @@ class ActivityStarter {
@Nullable String callingFeatureId;
int realCallingPid = DEFAULT_REAL_CALLING_PID;
int realCallingUid = DEFAULT_REAL_CALLING_UID;
+ int intentCreatorUid = DEFAULT_INTENT_CREATOR_UID;
+ String intentCreatorPackage;
int startFlags;
SafeActivityOptions activityOptions;
boolean ignoreTargetSecurity;
@@ -418,7 +434,7 @@ class ActivityStarter {
WaitResult waitResult;
int filterCallingUid;
PendingIntentRecord originatingPendingIntent;
- BackgroundStartPrivileges forcedBalByPiSender;
+ boolean allowBalExemptionForSystemProcess;
boolean freezeScreen;
final StringBuilder logMessage = new StringBuilder();
@@ -463,6 +479,8 @@ class ActivityStarter {
callingPid = DEFAULT_CALLING_PID;
callingUid = DEFAULT_CALLING_UID;
callingPackage = null;
+ intentCreatorUid = DEFAULT_INTENT_CREATOR_UID;
+ intentCreatorPackage = null;
callingFeatureId = null;
realCallingPid = DEFAULT_REAL_CALLING_PID;
realCallingUid = DEFAULT_REAL_CALLING_UID;
@@ -482,7 +500,7 @@ class ActivityStarter {
allowPendingRemoteAnimationRegistryLookup = true;
filterCallingUid = UserHandle.USER_NULL;
originatingPendingIntent = null;
- forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ allowBalExemptionForSystemProcess = false;
freezeScreen = false;
errorCallbackToken = null;
}
@@ -526,7 +544,7 @@ class ActivityStarter {
= request.allowPendingRemoteAnimationRegistryLookup;
filterCallingUid = request.filterCallingUid;
originatingPendingIntent = request.originatingPendingIntent;
- forcedBalByPiSender = request.forcedBalByPiSender;
+ allowBalExemptionForSystemProcess = request.allowBalExemptionForSystemProcess;
freezeScreen = request.freezeScreen;
errorCallbackToken = request.errorCallbackToken;
}
@@ -555,12 +573,14 @@ class ActivityStarter {
// "resolved" calling UID, where we try our best to identify the
// actual caller that is starting this activity
int resolvedCallingUid = callingUid;
+ String resolvedCallingPackage = callingPackage;
if (caller != null) {
synchronized (supervisor.mService.mGlobalLock) {
final WindowProcessController callerApp = supervisor.mService
.getProcessController(caller);
if (callerApp != null) {
resolvedCallingUid = callerApp.mInfo.uid;
+ resolvedCallingPackage = callerApp.mInfo.packageName;
}
}
}
@@ -596,7 +616,22 @@ class ActivityStarter {
// Collect information about the target of the Intent.
activityInfo = supervisor.resolveActivity(intent, resolveInfo, startFlags,
profilerInfo);
-
+ // Check if the Intent was redirected
+ if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
+ != 0) {
+ logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
+ "Unparceled intent does not have a creator token set.", intent,
+ intentCreatorUid, intentCreatorPackage, resolvedCallingUid,
+ resolvedCallingPackage, null);
+ }
+ if (IntentCreatorToken.isValid(intent)) {
+ IntentCreatorToken creatorToken = (IntentCreatorToken) intent.getCreatorToken();
+ if (creatorToken.getCreatorUid() != resolvedCallingUid) {
+ intentCreatorUid = creatorToken.getCreatorUid();
+ intentCreatorPackage = creatorToken.getCreatorPackage();
+ }
+ // leave intentCreatorUid as -1 if the intent creator is the same as the launcher
+ }
// Carefully collect grants without holding lock
if (activityInfo != null) {
if (android.security.Flags.contentUriPermissionApis()) {
@@ -606,11 +641,50 @@ class ActivityStarter {
UserHandle.getUserId(activityInfo.applicationInfo.uid),
activityInfo.requireContentUriPermissionFromCaller,
/* requestHashCode */ this.hashCode());
+ if (intentCreatorUid != DEFAULT_INTENT_CREATOR_UID) {
+ try {
+ NeededUriGrants creatorIntentGrants = supervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, intentCreatorUid,
+ activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(activityInfo.applicationInfo.uid),
+ activityInfo.requireContentUriPermissionFromCaller,
+ /* requestHashCode */ this.hashCode());
+ if (intentGrants == null) {
+ intentGrants = creatorIntentGrants;
+ } else {
+ intentGrants.merge(creatorIntentGrants);
+ }
+ } catch (SecurityException securityException) {
+ logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
+ "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
+ intentCreatorPackage, resolvedCallingUid,
+ resolvedCallingPackage, securityException);
+ }
+ }
} else {
intentGrants = supervisor.mService.mUgmInternal
.checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
activityInfo.applicationInfo.packageName,
UserHandle.getUserId(activityInfo.applicationInfo.uid));
+ if (intentCreatorUid != DEFAULT_INTENT_CREATOR_UID && intentGrants != null) {
+ try {
+ NeededUriGrants creatorIntentGrants = supervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, intentCreatorUid,
+ activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(
+ activityInfo.applicationInfo.uid));
+ if (intentGrants == null) {
+ intentGrants = creatorIntentGrants;
+ } else {
+ intentGrants.merge(creatorIntentGrants);
+ }
+ } catch (SecurityException securityException) {
+ logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
+ "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
+ intentCreatorPackage, resolvedCallingUid,
+ resolvedCallingPackage, securityException);
+ }
+ }
}
}
}
@@ -818,7 +892,10 @@ class ActivityStarter {
final ActivityOptions originalOptions = mRequest.activityOptions != null
? mRequest.activityOptions.getOriginalOptions() : null;
// Only track the launch time of activity that will be resumed.
- launchingRecord = mDoResume ? mLastStartActivityRecord : null;
+ if (mDoResume || (isStartResultSuccessful(res)
+ && mLastStartActivityRecord.getTask().isVisibleRequested())) {
+ launchingRecord = mLastStartActivityRecord;
+ }
// If the new record is the one that started, a new activity has created.
final boolean newActivityCreated = mStartActivity == launchingRecord;
// Notify ActivityMetricsLogger that the activity has launched.
@@ -976,6 +1053,8 @@ class ActivityStarter {
int requestCode = request.requestCode;
int callingPid = request.callingPid;
int callingUid = request.callingUid;
+ int intentCreatorUid = request.intentCreatorUid;
+ String intentCreatorPackage = request.intentCreatorPackage;
String callingPackage = request.callingPackage;
String callingFeatureId = request.callingFeatureId;
final int realCallingPid = request.realCallingPid;
@@ -1028,6 +1107,7 @@ class ActivityStarter {
if (requestCode >= 0 && !sourceRecord.finishing) {
resultRecord = sourceRecord;
}
+ request.logMessage.append(" (sr=" + System.identityHashCode(sourceRecord) + ")");
}
}
@@ -1171,6 +1251,39 @@ class ActivityStarter {
abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
callingPackage);
+ if (intentCreatorUid != Request.DEFAULT_INTENT_CREATOR_UID) {
+ try {
+ if (!mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
+ requestCode, 0, intentCreatorUid, intentCreatorPackage, "",
+ request.ignoreTargetSecurity, inTask != null, null, resultRecord,
+ resultRootTask)) {
+ abort = logAndAbortForIntentRedirect(mService.mContext,
+ "Creator checkStartAnyActivityPermission Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid,
+ callingPackage);
+ }
+ } catch (SecurityException e) {
+ logAndThrowExceptionForIntentRedirect(mService.mContext,
+ "Creator checkStartAnyActivityPermission Caused Exception.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage,
+ e);
+ }
+ if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid,
+ 0, resolvedType, aInfo.applicationInfo)) {
+ abort = logAndAbortForIntentRedirect(mService.mContext,
+ "Creator IntentFirewall.checkStartActivity Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
+ }
+
+ if (!mService.getPermissionPolicyInternal().checkStartActivity(intent,
+ intentCreatorUid, intentCreatorPackage)) {
+ abort = logAndAbortForIntentRedirect(mService.mContext,
+ "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
+ }
+ }
+ intent.removeCreatorToken();
+
// Merge the two options bundles, while realCallerOptions takes precedence.
ActivityOptions checkedOptions = options != null
? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
@@ -1191,7 +1304,7 @@ class ActivityStarter {
realCallingPid,
callerApp,
request.originatingPendingIntent,
- request.forcedBalByPiSender,
+ request.allowBalExemptionForSystemProcess,
resultRecord,
intent,
checkedOptions);
@@ -1223,7 +1336,8 @@ class ActivityStarter {
final TaskDisplayArea suggestedLaunchDisplayArea =
computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
- mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
+ mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags,
+ callingPackage,
callingFeatureId);
if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
@@ -1261,7 +1375,8 @@ class ActivityStarter {
if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
aInfo.packageName, userId)) {
final IIntentSender target = mService.getIntentSenderLocked(
- ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingFeatureId,
+ ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+ callingFeatureId,
callingUid, userId, null, null, 0, new Intent[]{intent},
new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT, null);
@@ -1324,7 +1439,8 @@ class ActivityStarter {
// app [on install success].
if (rInfo != null && rInfo.auxiliaryInfo != null) {
intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent,
- callingPackage, callingFeatureId, verificationBundle, resolvedType, userId);
+ callingPackage, callingFeatureId, verificationBundle, resolvedType,
+ userId);
resolvedType = null;
callingUid = realCallingUid;
callingPid = realCallingPid;
@@ -1647,6 +1763,13 @@ class ActivityStarter {
startedActivityRootTask.setAlwaysOnTop(true);
}
+ if (isIndependentLaunch && !mDoResume && avoidMoveToFront() && !mTransientLaunch
+ && !started.shouldBeVisible(true /* ignoringKeyguard */)) {
+ Slog.i(TAG, "Abort " + transition + " of invisible launch " + started);
+ transition.abort();
+ return startedActivityRootTask;
+ }
+
// If there is no state change (e.g. a resumed activity is reparented to top of
// another display) to trigger a visibility/configuration checking, we have to
// update the configuration for changing to different display.
@@ -1749,12 +1872,13 @@ class ActivityStarter {
mIntent.setFlags(mLaunchFlags);
boolean dreamStopping = false;
-
- for (ActivityRecord stoppingActivity : mSupervisor.mStoppingActivities) {
- if (stoppingActivity.getActivityType()
- == WindowConfiguration.ACTIVITY_TYPE_DREAM) {
- dreamStopping = true;
- break;
+ if (!com.android.window.flags.Flags.removeActivityStarterDreamCallback()) {
+ for (ActivityRecord stoppingActivity : mSupervisor.mStoppingActivities) {
+ if (stoppingActivity.getActivityType()
+ == WindowConfiguration.ACTIVITY_TYPE_DREAM) {
+ dreamStopping = true;
+ break;
+ }
}
}
@@ -1878,8 +2002,21 @@ class ActivityStarter {
if (mDoResume) {
if (!avoidMoveToFront()) {
mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
- if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
- && !dreamStopping) {
+
+ final boolean launchBehindDream;
+ if (com.android.window.flags.Flags.removeActivityStarterDreamCallback()) {
+ final TaskDisplayArea tda = mTargetRootTask.getTaskDisplayArea();
+ final Task top = (tda != null ? tda.getTopRootTask() : null);
+ launchBehindDream = (top != null && top != mTargetRootTask)
+ && top.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ && top.getTopNonFinishingActivity() != null;
+ } else {
+ launchBehindDream = !mTargetRootTask.isTopRootTaskInDisplayArea()
+ && mService.isDreaming()
+ && !dreamStopping;
+ }
+
+ if (launchBehindDream) {
// Launching underneath dream activity (fullscreen, always-on-top). Run the
// launch--behind transition so the Activity gets created and starts
// in visible state.
@@ -2216,7 +2353,8 @@ class ActivityStarter {
// When there is a reused activity and the current result is a trampoline activity,
// set the reused activity as the result.
if (mLastStartActivityRecord != null
- && (mLastStartActivityRecord.finishing || mLastStartActivityRecord.noDisplay)) {
+ && (mLastStartActivityRecord.finishing
+ || mLastStartActivityRecord.isNoDisplay())) {
mLastStartActivityRecord = targetTaskTop;
}
@@ -2243,6 +2381,9 @@ class ActivityStarter {
return START_SUCCESS;
}
+ if (mMovedToTopActivity != null) {
+ targetTaskTop = mMovedToTopActivity;
+ }
// The reusedActivity could be finishing, for example of starting an activity with
// FLAG_ACTIVITY_CLEAR_TOP flag. In that case, use the top running activity in the
// task instead.
@@ -2264,7 +2405,8 @@ class ActivityStarter {
// This is moving an existing task to front. But since dream activity has a higher z-order
// to cover normal activities, it needs the awakening event to be dismissed.
if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
- targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+ targetTaskTop.mTaskSupervisor.wakeUp(
+ targetTaskTop.getDisplayId(), "recycleTask#turnScreenOnFlag");
}
mLastStartActivityRecord = targetTaskTop;
@@ -2481,7 +2623,7 @@ class ActivityStarter {
mInTaskFragment = null;
mAddingToTaskFragment = null;
mAddingToTask = false;
-
+ mMovedToTopActivity = null;
mSourceRootTask = null;
mTargetRootTask = null;
@@ -2743,10 +2885,7 @@ class ActivityStarter {
mInTask = null;
// Launch ResolverActivity in the source task, so that it stays in the task bounds
// when in freeform workspace.
- // Also put noDisplay activities in the source task. These by itself can be placed
- // in any task/root-task, however it could launch other activities like
- // ResolverActivity, and we want those to stay in the original task.
- if ((mStartActivity.isResolverOrDelegateActivity() || mStartActivity.noDisplay)
+ if (mStartActivity.isResolverOrDelegateActivity()
&& mSourceRecord != null && mSourceRecord.inFreeformWindowingMode()) {
mAddingToTask = true;
}
@@ -2773,10 +2912,22 @@ class ActivityStarter {
}
}
- if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
- && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) {
- // ignore the flag if there is no the sourceRecord or without new_task flag
- mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
+ if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
+ final boolean hasNewTaskFlag = (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0;
+ if (!hasNewTaskFlag || mSourceRecord == null) {
+ // ignore the flag if there is no the sourceRecord or without new_task flag
+ Slog.w(TAG_WM, !hasNewTaskFlag
+ ? "Launch adjacent ignored due to missing NEW_TASK"
+ : "Launch adjacent ignored due to missing source activity");
+ mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
+ }
+ // Ensure that the source task or its parents has not disabled launch-adjacent
+ if (mSourceRecord != null && mSourceRecord.getTask() != null &&
+ mSourceRecord.getTask().isLaunchAdjacentDisabled()) {
+ Slog.w(TAG_WM, "Launch adjacent blocked by source task or ancestor");
+ mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
+ }
+
}
}
@@ -3282,6 +3433,16 @@ class ActivityStarter {
return this;
}
+ ActivityStarter setIntentCreatorUid(int uid) {
+ mRequest.intentCreatorUid = uid;
+ return this;
+ }
+
+ ActivityStarter setIntentCreatorPackage(String intentCreatorPackage) {
+ mRequest.intentCreatorPackage = intentCreatorPackage;
+ return this;
+ }
+
/**
* Sets the pid of the caller who requested to launch the activity.
*
@@ -3316,8 +3477,8 @@ class ActivityStarter {
return this;
}
- ActivityStarter setActivityOptions(Bundle bOptions) {
- return setActivityOptions(SafeActivityOptions.fromBundle(bOptions));
+ ActivityStarter setActivityOptions(Bundle bOptions, int callingPid, int callingUid) {
+ return setActivityOptions(SafeActivityOptions.fromBundle(bOptions, callingPid, callingUid));
}
ActivityStarter setIgnoreTargetSecurity(boolean ignoreTargetSecurity) {
@@ -3380,8 +3541,9 @@ class ActivityStarter {
return this;
}
- ActivityStarter setBackgroundStartPrivileges(BackgroundStartPrivileges forcedBalByPiSender) {
- mRequest.forcedBalByPiSender = forcedBalByPiSender;
+ ActivityStarter setAllowBalExemptionForSystemProcess(
+ boolean allowBalExemptionForSystemProcess) {
+ mRequest.allowBalExemptionForSystemProcess = allowBalExemptionForSystemProcess;
return this;
}
@@ -3441,4 +3603,48 @@ class ActivityStarter {
pw.print(" mInTaskFragment=");
pw.println(mInTaskFragment);
}
+
+ static void logAndThrowExceptionForIntentRedirect(@NonNull Context context,
+ @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+ @Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage,
+ @Nullable SecurityException originalException) {
+ String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+ intentCreatorPackage, callingUid, callingPackage);
+ Slog.wtf(TAG, msg);
+ if (preventIntentRedirectShowToast()) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(context,
+ "Activity launch blocked. go/report-bug-intentRedir to report a bug",
+ Toast.LENGTH_LONG).show());
+ }
+ if (preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
+ ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid)) {
+ throw new SecurityException(msg, originalException);
+ }
+ }
+
+ private static boolean logAndAbortForIntentRedirect(@NonNull Context context,
+ @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+ @Nullable String intentCreatorPackage, int callingUid,
+ @Nullable String callingPackage) {
+ String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+ intentCreatorPackage, callingUid, callingPackage);
+ Slog.wtf(TAG, msg);
+ if (preventIntentRedirectShowToast()) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(context,
+ "Activity launch blocked. go/report-bug-intentRedir to report a bug",
+ Toast.LENGTH_LONG).show());
+ }
+ return preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
+ ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid);
+ }
+
+ private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
+ @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
+ int callingUid, @Nullable String callingPackage) {
+ return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+ + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
+ + "; callingPackage: " + callingPackage + "; intent: " + intent;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 3d6b64b2e536..0a57cb50d681 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppProtoEnums;
-import android.app.BackgroundStartPrivileges;
import android.app.IActivityManager;
import android.app.IAppTask;
import android.app.IApplicationThread;
@@ -179,15 +178,15 @@ public abstract class ActivityTaskManagerInternal {
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
* null if not originated by PendingIntent
- * @param forcedBalByPiSender If set to allow, the
- * PendingIntent's sender will try to force allow background activity starts.
+ * @param allowBalExemptionForSystemProcess If set to {@code true}, the
+ * PendingIntent's sender will allow additional exemptions.
* This is only possible if the sender of the PendingIntent is a system process.
*/
public abstract int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid,
String callingPackage, @Nullable String callingFeatureId, Intent[] intents,
String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender);
+ boolean allowBalExemptionForSystemProcess);
/**
* Start intent as a package.
@@ -202,8 +201,8 @@ public abstract class ActivityTaskManagerInternal {
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
* null if not originated by PendingIntent
- * @param forcedBalByPiSender If set to allow, the
- * PendingIntent's sender will try to force allow background activity starts.
+ * @param allowBalExemptionForSystemProcess If set to {@code true}, the
+ * PendingIntent's sender will allow additional exemptions.
* This is only possible if the sender of the PendingIntent is a system process.
*/
public abstract int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
@@ -211,7 +210,7 @@ public abstract class ActivityTaskManagerInternal {
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason,
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender);
+ boolean allowBalExemptionForSystemProcess);
/**
* Callback to be called on certain activity start scenarios.
@@ -589,7 +588,7 @@ public abstract class ActivityTaskManagerInternal {
* sensitive environment.
*/
public abstract TaskSnapshot getTaskSnapshotBlocking(int taskId,
- boolean isLowResolution);
+ boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage);
/** Returns true if uid is considered foreground for activity start purposes. */
public abstract boolean isUidForeground(int uid);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 20acad37d536..afa7ea136c77 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -71,12 +71,12 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DREAM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IMMERSIVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_LOCKTASK;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
@@ -145,7 +145,6 @@ import android.app.AlertDialog;
import android.app.AnrController;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.BackgroundStartPrivileges;
import android.app.Dialog;
import android.app.IActivityClientController;
import android.app.IActivityController;
@@ -1240,12 +1239,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
assertPackageMatchesCallingUid(callingPackage);
final String reason = "startActivities";
enforceNotIsolatedCaller(reason);
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason);
+ if (intents != null) {
+ for (Intent intent : intents) {
+ mAmInternal.addCreatorToken(intent, callingPackage);
+ }
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ userId = handleIncomingUser(callingPid, callingUid, userId, reason);
// TODO: Switch to user app stacks here.
return getActivityStartController().startActivities(caller, -1, 0, -1, callingPackage,
callingFeatureId, intents, resolvedTypes, resultTo,
- SafeActivityOptions.fromBundle(bOptions), userId, reason,
- null /* originatingPendingIntent */, BackgroundStartPrivileges.NONE);
+ SafeActivityOptions.fromBundle(bOptions, callingPid, callingUid), userId, reason,
+ null /* originatingPendingIntent */, false);
}
@Override
@@ -1269,7 +1275,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
- final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
+ mAmInternal.addCreatorToken(intent, callingPackage);
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final SafeActivityOptions opts = SafeActivityOptions.fromBundle(
+ bOptions, callingPid, callingUid);
assertPackageMatchesCallingUid(callingPackage);
enforceNotIsolatedCaller("startActivityAsUser");
@@ -1278,11 +1288,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
SdkSandboxManagerLocal.class);
sdkSandboxManagerLocal.enforceAllowedToHostSandboxedActivity(
- intent, Binder.getCallingUid(), callingPackage
+ intent, callingUid, callingPackage
);
}
- if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ if (Process.isSdkSandboxUid(callingUid)) {
SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
SdkSandboxManagerLocal.class);
if (sdkSandboxManagerLocal == null) {
@@ -1293,7 +1303,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
- Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");
+ callingPid, callingUid, "startActivityAsUser");
// TODO: Switch to user app stacks here.
return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
@@ -1331,6 +1341,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
PendingIntentRecord pir = (PendingIntentRecord) target;
+ if (fillInIntent != null) {
+ mAmInternal.addCreatorToken(fillInIntent, pir.getPackageName());
+ }
+
synchronized (mGlobalLock) {
// If this is coming from the currently resumed activity, it is
// effectively saying that app switches are allowed at this point.
@@ -1341,6 +1355,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mAppSwitchesState = APP_SWITCH_ALLOW;
}
}
+
return pir.sendInner(caller, 0, fillInIntent, resolvedType, allowlistToken, null, null,
resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions);
}
@@ -1352,7 +1367,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (intent != null && intent.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions);
+
+ final int origCallingPid = Binder.getCallingPid();
+ final int origCallingUid = Binder.getCallingUid();
+ SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions, origCallingPid,
+ origCallingUid);
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(callingActivity);
@@ -1365,6 +1384,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
SafeActivityOptions.abort(options);
return false;
}
+
+ mAmInternal.addCreatorToken(intent, r.packageName);
+
intent = new Intent(intent);
// Remove existing mismatch flag so it can be properly updated later
intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
@@ -1438,13 +1460,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
resultTo.removeResultsLocked(r, resultWho, requestCode);
}
- final int origCallingUid = Binder.getCallingUid();
- final int origCallingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
// TODO(b/64750076): Check if calling pid should really be -1.
try {
if (options == null) {
- options = new SafeActivityOptions(ActivityOptions.makeBasic());
+ options = new SafeActivityOptions(ActivityOptions.makeBasic(),
+ Binder.getCallingPid(), Binder.getCallingUid());
}
// Fixes b/230492947 b/337726734
@@ -1545,7 +1566,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// To start the dream from background, we need to start it from a persistent
// system process. Here we set the real calling uid to the system server uid
.setRealCallingUid(Binder.getCallingUid())
- .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+ .setAllowBalExemptionForSystemProcess(true)
.execute();
final ActivityRecord started = outActivity[0];
@@ -1565,8 +1586,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
assertPackageMatchesCallingUid(callingPackage);
final WaitResult res = new WaitResult();
enforceNotIsolatedCaller("startActivityAndWait");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, "startActivityAndWait");
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ userId = handleIncomingUser(callingPid, callingUid, userId, "startActivityAndWait");
// TODO: Switch to user app stacks here.
getActivityStartController().obtainStarter(intent, "startActivityAndWait")
.setCaller(caller)
@@ -1577,7 +1599,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setStartFlags(startFlags)
- .setActivityOptions(bOptions)
+ .setActivityOptions(bOptions, callingPid, callingUid)
.setUserId(userId)
.setProfilerInfo(profilerInfo)
.setWaitResult(res)
@@ -1592,8 +1614,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
Bundle bOptions, int userId) {
assertPackageMatchesCallingUid(callingPackage);
enforceNotIsolatedCaller("startActivityWithConfig");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- "startActivityWithConfig");
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ userId = handleIncomingUser(callingPid, callingUid, userId, "startActivityWithConfig");
// TODO: Switch to user app stacks here.
return getActivityStartController().obtainStarter(intent, "startActivityWithConfig")
.setCaller(caller)
@@ -1605,7 +1628,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setRequestCode(requestCode)
.setStartFlags(startFlags)
.setGlobalConfiguration(config)
- .setActivityOptions(bOptions)
+ .setActivityOptions(bOptions, callingPid, callingUid)
.setUserId(userId)
.execute();
}
@@ -1698,7 +1721,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setFilterCallingUid(isResolver ? 0 /* system */ : targetUid)
// The target may well be in the background, which would normally prevent it
// from starting an activity. Here we definitely want the start to succeed.
- .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+ .setAllowBalExemptionForSystemProcess(true)
.execute();
} catch (SecurityException e) {
// XXX need to figure out how to propagate to original app.
@@ -1744,7 +1767,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setProfilerInfo(profilerInfo)
.setActivityOptions(createSafeActivityOptionsWithBalAllowed(bOptions))
.setUserId(userId)
- .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+ .setAllowBalExemptionForSystemProcess(true)
.execute();
}
@@ -1771,7 +1794,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setResolvedType(resolvedType)
.setActivityOptions(createSafeActivityOptionsWithBalAllowed(bOptions))
.setUserId(userId)
- .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+ .setAllowBalExemptionForSystemProcess(true)
.execute();
} finally {
Binder.restoreCallingIdentity(origId);
@@ -1809,7 +1832,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
- final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(bOptions);
+ final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ bOptions, callingPid, callingUid);
final long origId = Binder.clearCallingIdentity();
try {
return mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, taskId,
@@ -1833,8 +1857,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
assertPackageMatchesCallingUid(callingPackage);
+ mAmInternal.addCreatorToken(intent, callingPackage);
+
final ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setLaunchTaskId(taskId);
+ // Pass in the system UID to allow setting launch taskId with MANAGE_GAME_ACTIVITY.
+ final SafeActivityOptions safeOptions = new SafeActivityOptions(
+ activityOptions, Process.myPid(), Process.SYSTEM_UID);
userId = handleIncomingUser(callingPid, callingUid, userId, "startActivityFromGameSession");
@@ -1848,7 +1877,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setCallingPackage(intent.getPackage())
.setCallingFeatureId(callingFeatureId)
.setUserId(userId)
- .setActivityOptions(activityOptions.toBundle())
+ .setActivityOptions(safeOptions)
.setRealCallingUid(Binder.getCallingUid())
.execute();
} finally {
@@ -2217,8 +2246,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
ProtoLog.d(WM_DEBUG_TASKS, "moveTaskToFront: moving taskId=%d", taskId);
synchronized (mGlobalLock) {
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
moveTaskToFrontLocked(appThread, callingPackage, taskId, flags,
- SafeActivityOptions.fromBundle(bOptions));
+ SafeActivityOptions.fromBundle(bOptions, callingPid, callingUid));
}
}
@@ -2243,7 +2274,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
-1,
callerApp,
null,
- BackgroundStartPrivileges.NONE,
+ false,
null,
null,
null);
@@ -3766,6 +3797,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
r.setPictureInPictureParams(params);
enterPipTransition.setPipActivity(r);
r.mAutoEnteringPip = isAutoEnter;
+
+ if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
+ && enterPipTransition != null) {
+ enterPipTransition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
getTransitionController().startCollectOrQueue(enterPipTransition, (deferred) -> {
getTransitionController().requestStartTransition(enterPipTransition,
r.getTask(), null /* remoteTransition */, null /* displayChange */);
@@ -3887,6 +3924,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
+ public void requestOpenInBrowserEducation(IBinder appToken) {
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInRootTaskLocked(appToken);
+ if (r != null) {
+ r.requestOpenInBrowserEducation();
+ }
+ }
+ }
+
+ @Override
public boolean updateConfiguration(Configuration values) {
mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
@@ -3938,6 +3985,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ private TaskSnapshot getTaskSnapshotInner(int taskId, boolean isLowResolution,
+ @TaskSnapshot.ReferenceFlags int usage) {
+ final Task task;
+ synchronized (mGlobalLock) {
+ task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
+ if (task == null) {
+ Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
+ return null;
+ }
+ // Try to load snapshot from cache first, and add reference if the snapshot is in cache.
+ final TaskSnapshot snapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+ task.mUserId, false /* restoreFromDisk */, isLowResolution);
+ if (snapshot != null) {
+ snapshot.addReference(usage);
+ return snapshot;
+ }
+ }
+ // Don't call this while holding the lock as this operation might hit the disk.
+ return mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+ task.mUserId, true /* restoreFromDisk */, isLowResolution);
+ }
+
@Override
public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
@@ -5848,6 +5917,29 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
/**
+ * Registers an app that uses the Strict Mode for detecting BAL.
+ *
+ * @param callback the callback to register
+ * @return {@code true} if the callback was registered successfully.
+ */
+ @Override
+ public boolean registerBackgroundActivityStartCallback(IBinder callback) {
+ return mTaskSupervisor.getBackgroundActivityLaunchController()
+ .addStrictModeCallback(Binder.getCallingUid(), callback);
+ }
+
+ /**
+ * Unregisters an app that uses the Strict Mode for detecting BAL.
+ *
+ * @param callback the callback to unregister
+ */
+ @Override
+ public void unregisterBackgroundActivityStartCallback(IBinder callback) {
+ mTaskSupervisor.getBackgroundActivityLaunchController()
+ .removeStrictModeCallback(Binder.getCallingUid(), callback);
+ }
+
+ /**
* Wrap the {@link ActivityOptions} in {@link SafeActivityOptions} and attach caller options
* that allow using the callers permissions to start background activities.
*/
@@ -5861,7 +5953,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
options.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
}
- return new SafeActivityOptions(options);
+ return new SafeActivityOptions(options, Binder.getCallingPid(), Binder.getCallingUid());
}
/**
@@ -6026,12 +6118,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
Binder.restoreCallingIdentity(ident);
}
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
return getActivityStartController().startActivitiesInPackage(
packageUid, packageName, featureId,
intents, resolvedTypes, null /* resultTo */,
- SafeActivityOptions.fromBundle(bOptions), userId,
+ SafeActivityOptions.fromBundle(bOptions, callingPid, callingUid), userId,
false /* validateIncomingUser */, null /* originatingPendingIntent */,
- BackgroundStartPrivileges.NONE);
+ false);
}
@Override
@@ -6039,12 +6133,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
String callingPackage, @Nullable String callingFeatureId, Intent[] intents,
String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender) {
+ boolean allowBalExemptionForSystemProcess) {
assertPackageMatchesCallingUid(callingPackage);
return getActivityStartController().startActivitiesInPackage(uid, realCallingPid,
realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes,
resultTo, options, userId, validateIncomingUser, originatingPendingIntent,
- forcedBalByPiSender);
+ allowBalExemptionForSystemProcess);
}
@Override
@@ -6053,13 +6147,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason,
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender) {
+ boolean allowBalExemptionForSystemProcess) {
assertPackageMatchesCallingUid(callingPackage);
return getActivityStartController().startActivityInPackage(uid, realCallingPid,
realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
reason, validateIncomingUser, originatingPendingIntent,
- forcedBalByPiSender);
+ allowBalExemptionForSystemProcess);
}
@Override
@@ -6090,7 +6184,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
.setRealCallingUid(Binder.getCallingUid())
.setUserId(userId)
- .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+ .setAllowBalExemptionForSystemProcess(true)
.setFreezeScreen(true)
.execute();
}
@@ -6709,6 +6803,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
synchronized (mGlobalLockWithoutBoost) {
mTaskSupervisor.getActivityMetricsLogger().notifyBindApplication(wpc.mInfo);
wpc.onConfigurationChanged(getGlobalConfiguration());
+ // Let the application initialize with consistent configuration as its activity.
+ for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mStartingProcessActivities.get(i);
+ if (wpc.mUid == r.info.applicationInfo.uid && wpc.mName.equals(r.processName)) {
+ wpc.registerActivityConfigurationListener(r);
+ break;
+ }
+ }
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Binding proc %s with config %s",
+ wpc.mName, wpc.getConfiguration());
// The "info" can be the target of instrumentation.
return new PreBindInfo(compatibilityInfoForPackageLocked(info),
new Configuration(wpc.getConfiguration()));
@@ -7258,8 +7362,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public TaskSnapshot getTaskSnapshotBlocking(
- int taskId, boolean isLowResolution) {
- return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution);
+ int taskId, boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage) {
+ return ActivityTaskManagerService.this.getTaskSnapshotInner(
+ taskId, isLowResolution, usage);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index cebf1bee3439..b20558ff79cc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -52,8 +52,8 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
@@ -96,7 +96,6 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
-import android.app.BackgroundStartPrivileges;
import android.app.IActivityClientController;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
@@ -242,6 +241,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
static {
ACTION_TO_RUNTIME_PERMISSION.put(MediaStore.ACTION_IMAGE_CAPTURE,
Manifest.permission.CAMERA);
+ ACTION_TO_RUNTIME_PERMISSION.put(MediaStore.ACTION_MOTION_PHOTO_CAPTURE,
+ Manifest.permission.CAMERA);
ACTION_TO_RUNTIME_PERMISSION.put(MediaStore.ACTION_VIDEO_CAPTURE,
Manifest.permission.CAMERA);
ACTION_TO_RUNTIME_PERMISSION.put(Intent.ACTION_CALL,
@@ -343,6 +344,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
private ActivityRecord mTopResumedActivity;
/**
+ * Cached value of the topmost resumed activity that reported to the client.
+ */
+ private ActivityRecord mLastReportedTopResumedActivity;
+
+ /**
* Flag indicating whether we're currently waiting for the previous top activity to handle the
* loss of the state and report back before making new activity top resumed.
*/
@@ -964,11 +970,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// transaction.
mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread());
}
- mService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
- proc.getThread(), launchActivityItem, lifecycleItem,
+ mService.getLifecycleManager().scheduleTransactionItems(
+ proc.getThread(),
// Immediately dispatch the transaction, so that if it fails, the server can
// restart the process and retry now.
- true /* shouldDispatchImmediately */);
+ true /* shouldDispatchImmediately */,
+ launchActivityItem, lifecycleItem);
if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
// If the seq is increased, there should be something changed (e.g. registered
@@ -1729,9 +1736,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
return;
}
Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
- if (transit == null) {
- transit = task.mTransitionController.getCollectingTransition();
- }
if (transit != null) {
transit.collectClose(task);
if (!task.mTransitionController.useFullReadyTracking()) {
@@ -1743,7 +1747,15 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// before anything that may need it to wait (setReady(false)).
transit.setReady(task, true);
}
+ } else {
+ // If we failed to create a transition, there might be already a currently collecting
+ // transition. Let's use it if possible.
+ transit = task.mTransitionController.getCollectingTransition();
+ if (transit != null) {
+ transit.collectClose(task);
+ }
}
+
// Consume the stopping activities immediately so activity manager won't skip killing
// the process because it is still foreground state, i.e. RESUMED -> PAUSING set from
// removeActivities -> finishIfPossible.
@@ -2048,6 +2060,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
break;
}
}
+ long timeRemaining = endTime - System.currentTimeMillis();
+ mWindowManager.mSnapshotController.mTaskSnapshotController.waitFlush(timeRemaining);
// Force checkReadyForSleep to complete.
checkReadyForSleepLocked(false /* allowDelay */);
@@ -2292,6 +2306,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (topRootTask == null) {
// There's no focused task and there won't have any resumed activity either.
scheduleTopResumedActivityStateLossIfNeeded();
+ mTopResumedActivity = null;
}
if (mService.isSleepingLocked()) {
// There won't be a next resumed activity. The top process should still be updated
@@ -2335,25 +2350,27 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Schedule current top resumed activity state loss */
private void scheduleTopResumedActivityStateLossIfNeeded() {
- if (mTopResumedActivity == null) {
+ if (mLastReportedTopResumedActivity == null) {
return;
}
// mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
// before the prevTopActivity one hasn't reported back yet. So server never sent the top
// resumed state change message to prevTopActivity.
- if (!mTopResumedActivityWaitingForPrev
- && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
- scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+ if (!mTopResumedActivityWaitingForPrev && readyToResume()
+ && mLastReportedTopResumedActivity.scheduleTopResumedActivityChanged(
+ false /* onTop */)) {
+ scheduleTopResumedStateLossTimeout(mLastReportedTopResumedActivity);
mTopResumedActivityWaitingForPrev = true;
- mTopResumedActivity = null;
+ mLastReportedTopResumedActivity = null;
}
}
/** Schedule top resumed state change if previous top activity already reported back. */
private void scheduleTopResumedActivityStateIfNeeded() {
- if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
+ if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev && readyToResume()) {
mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */);
+ mLastReportedTopResumedActivity = mTopResumedActivity;
}
}
@@ -2479,7 +2496,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Notifies that the top activity of the task is forced to be resizeable. */
private void handleForcedResizableTaskIfNeeded(Task task, int reason) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
- if (topActivity == null || topActivity.noDisplay
+ if (topActivity == null || topActivity.isNoDisplay()
|| !topActivity.canForceResizeNonResizable(task.getWindowingMode())) {
return;
}
@@ -2526,9 +2543,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
- void wakeUp(String reason) {
+ void wakeUp(int displayId, String reason) {
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION,
- "android.server.am:TURN_ON:" + reason);
+ "android.server.am:TURN_ON:" + reason, displayId);
}
/** Starts a batch of visibility updates. */
@@ -2578,14 +2595,21 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
wpc.computeProcessActivityState();
}
- void computeProcessActivityStateBatch() {
+ boolean computeProcessActivityStateBatch() {
if (mActivityStateChangedProcs.isEmpty()) {
- return;
+ return false;
}
+ boolean changed = false;
for (int i = mActivityStateChangedProcs.size() - 1; i >= 0; i--) {
- mActivityStateChangedProcs.get(i).computeProcessActivityState();
+ final WindowProcessController wpc = mActivityStateChangedProcs.get(i);
+ final int prevState = wpc.getActivityStateFlags();
+ wpc.computeProcessActivityState();
+ if (!changed && prevState != wpc.getActivityStateFlags()) {
+ changed = true;
+ }
}
mActivityStateChangedProcs.clear();
+ return changed;
}
/**
@@ -2600,6 +2624,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
*/
void endDeferResume() {
mDeferResumeCount--;
+ if (readyToResume() && mLastReportedTopResumedActivity != null
+ && mTopResumedActivity != mLastReportedTopResumedActivity) {
+ scheduleTopResumedActivityStateLossIfNeeded();
+ }
}
/** @return True if resume can be called. */
@@ -2868,7 +2896,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
null, 0, 0, options, userId, task, "startActivityFromRecents",
false /* validateIncomingUser */, null /* originatingPendingIntent */,
- BackgroundStartPrivileges.NONE);
+ /* allowBalExemptionForSystemProcess */ false);
} finally {
SaferIntentUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
synchronized (mService.mGlobalLock) {
@@ -2888,10 +2916,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
private boolean mIncludeInvisibleAndFinishing;
private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(
- @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
+ ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
mIncludeInvisibleAndFinishing = true;
- mIgnoringKeyguard = ignoringKeyguard;
+ mIgnoringKeyguard = true;
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index f245efd7ff0e..086b11c25a8d 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -37,8 +37,10 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
+import static com.android.server.wm.AppCompatUtils.isDisplayIgnoreActivitySizeRestrictions;
import android.annotation.NonNull;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -126,18 +128,24 @@ class AppCompatAspectRatioOverrides {
return false;
}
- mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+ final int aspectRatio = getUserMinAspectRatioOverrideCode();
- return mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
- && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
- && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ return aspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+ && aspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
+ && aspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ }
+
+ boolean userPreferenceCompatibleWithNonResizability() {
+ final int aspectRatio = getUserMinAspectRatioOverrideCode();
+ return aspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+ || aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
boolean shouldApplyUserFullscreenOverride() {
if (isUserFullscreenOverrideEnabled()) {
- mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+ final int aspectRatio = getUserMinAspectRatioOverrideCode();
- return mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ return aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
return false;
@@ -153,10 +161,12 @@ class AppCompatAspectRatioOverrides {
}
boolean isSystemOverrideToFullscreenEnabled() {
+ final int aspectRatio = getUserMinAspectRatioOverrideCode();
+
return isChangeEnabled(mActivityRecord, OVERRIDE_ANY_ORIENTATION_TO_USER)
&& !mAllowOrientationOverrideOptProp.isFalse()
- && (mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
- || mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ && (aspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+ || aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
}
/**
@@ -172,13 +182,21 @@ class AppCompatAspectRatioOverrides {
&& mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
}
+ /**
+ * Whether to ignore fixed orientation, aspect ratio and resizability of activity.
+ */
boolean hasFullscreenOverride() {
- // `mUserAspectRatio` is always initialized first in `shouldApplyUserFullscreenOverride()`.
- return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled();
+ return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled()
+ || shouldIgnoreActivitySizeRestrictionsForDisplay();
+ }
+
+ boolean shouldIgnoreActivitySizeRestrictionsForDisplay() {
+ return isDisplayIgnoreActivitySizeRestrictions(mActivityRecord)
+ && !mAllowOrientationOverrideOptProp.isFalse();
}
float getUserMinAspectRatio() {
- switch (mUserAspectRatioState.mUserAspectRatio) {
+ switch (getUserMinAspectRatioOverrideCode()) {
case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
return getDisplaySizeMinAspectRatio();
case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
@@ -255,13 +273,10 @@ class AppCompatAspectRatioOverrides {
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
- final AppCompatCameraPolicy cameraPolicy =
- mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
- && (cameraPolicy != null
- && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
+ && AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
}
/**
@@ -272,13 +287,7 @@ class AppCompatAspectRatioOverrides {
}
int getUserMinAspectRatioOverrideCode() {
- try {
- return mActivityRecord.mAtmService.getPackageManager()
- .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
- }
- return mUserAspectRatioState.mUserAspectRatio;
+ return mUserAspectRatioState.getUserAspectRatio(mActivityRecord);
}
private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -303,10 +312,30 @@ class AppCompatAspectRatioOverrides {
}
private static class UserAspectRatioState {
- // TODO(b/315140179): Make mUserAspectRatio final
- // The min aspect ratio override set by user
+ // The min aspect ratio override set by the user.
@PackageManager.UserMinAspectRatio
private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
+ private boolean mHasBeenSet = false;
+
+ @PackageManager.UserMinAspectRatio
+ private int getUserAspectRatio(@NonNull ActivityRecord activityRecord) {
+ // Package manager can be null at construction time, so access should be on demand.
+ if (!mHasBeenSet) {
+ try {
+ final IPackageManager pm = activityRecord.mAtmService.getPackageManager();
+ if (pm != null) {
+ mUserAspectRatio = pm.getUserMinAspectRatio(activityRecord.packageName,
+ activityRecord.mUserId);
+ mHasBeenSet = true;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception thrown retrieving aspect ratio user override "
+ + this, e);
+ }
+ }
+
+ return mUserAspectRatio;
+ }
}
private Resources getResources() {
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 51ef87dcab1b..e8eae4f96a04 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -70,7 +70,7 @@ class AppCompatAspectRatioPolicy {
mAppCompatAspectRatioState.reset();
}
- float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
+ private float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
@NonNull Rect parentBounds) {
final float letterboxAspectRatioOverride =
mAppCompatOverrides.getAppCompatAspectRatioOverrides()
@@ -113,22 +113,28 @@ class AppCompatAspectRatioPolicy {
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMinAspectRatio();
}
+
final ActivityInfo info = mActivityRecord.info;
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
+
+ // If in camera compat mode, aspect ratio from the camera compat policy has priority over
+ // the default aspect ratio.
+ if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(mActivityRecord)) {
+ return Math.max(AppCompatCameraPolicy.getCameraCompatMinAspectRatio(mActivityRecord),
+ info.getMinAspectRatio());
}
+
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final DisplayContent displayContent = mActivityRecord.getDisplayContent();
- final boolean shouldOverrideMinAspectRatioForCamera = displayContent != null
- && displayContent.mAppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(
- mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
- && !shouldOverrideMinAspectRatioForCamera) {
- return info.getMinAspectRatio();
+ && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
+ final float minAspectRatio = info.getMinAspectRatio();
+ if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
+ return minAspectRatio;
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -170,7 +176,11 @@ class AppCompatAspectRatioPolicy {
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
- return mActivityRecord.info.getMaxAspectRatio();
+ final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+ if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
+ return maxAspectRatio;
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 241390c12818..47d30c98120c 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -16,10 +16,9 @@
package com.android.server.wm;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
@@ -33,7 +32,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
-import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
import com.android.server.wm.utils.OptPropFactory;
import com.android.window.flags.Flags;
@@ -165,13 +163,15 @@ class AppCompatCameraOverrides {
*
* <p>The treatment is enabled when the following conditions are met:
* <ul>
- * <li>Property gating the camera compatibility free-form treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override.
+ * <li>Feature flag gating the camera compatibility free-form treatment is enabled.
+ * <li>Activity is opted-in using per-app override, or the treatment is enabled for all apps.
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.cameraCompatForFreeform() && !isChangeEnabled(mActivityRecord,
- OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
+ return Flags.enableCameraCompatForDesktopWindowing() && (isChangeEnabled(mActivityRecord,
+ OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
+ || mActivityRecord.mWmService.mAppCompatConfiguration
+ .isCameraCompatFreeformWindowingTreatmentEnabled());
}
boolean isOverrideOrientationOnlyForCameraEnabled() {
@@ -202,22 +202,10 @@ class AppCompatCameraOverrides {
&& !mActivityRecord.shouldCreateAppCompatDisplayInsets();
}
- @FreeformCameraCompatMode
- int getFreeformCameraCompatMode() {
- return mAppCompatCameraOverridesState.mFreeformCameraCompatMode;
- }
-
- void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
- mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
- }
-
static class AppCompatCameraOverridesState {
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
// DisplayRotationCompatPolicy.
private boolean mIsRefreshRequested;
-
- @FreeformCameraCompatMode
- private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 67bfd7605128..8be66ccfbd70 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -18,8 +18,11 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
import android.widget.Toast;
@@ -48,8 +51,9 @@ class AppCompatCameraPolicy {
// without the need to restart the device.
final boolean needsDisplayRotationCompatPolicy =
wmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
- final boolean needsCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform()
- && DesktopModeHelper.canEnterDesktopMode(wmService.mContext);
+ final boolean needsCameraCompatFreeformPolicy =
+ Flags.enableCameraCompatForDesktopWindowing()
+ && DesktopModeHelper.canEnterDesktopMode(wmService.mContext);
if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) {
mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH);
mActivityRefresher = new ActivityRefresher(wmService, wmService.mH);
@@ -67,22 +71,30 @@ class AppCompatCameraPolicy {
}
}
- void onActivityRefreshed(@NonNull ActivityRecord activity) {
- if (mActivityRefresher != null) {
- mActivityRefresher.onActivityRefreshed(activity);
+ static void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+ cameraPolicy.mActivityRefresher.onActivityRefreshed(activity);
}
}
+ @Nullable
+ static AppCompatCameraPolicy getAppCompatCameraPolicy(@NonNull ActivityRecord activityRecord) {
+ return activityRecord.mDisplayContent != null
+ ? activityRecord.mDisplayContent.mAppCompatCameraPolicy : null;
+ }
+
/**
* "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
* This allows to clear cached values in apps (e.g. display or camera rotation) that influence
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*/
- void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+ static void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
- if (mActivityRefresher != null) {
- mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+ cameraPolicy.mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
lastReportedConfig);
}
}
@@ -99,11 +111,11 @@ class AppCompatCameraPolicy {
}
}
- boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.isActivityEligibleForOrientationOverride(activity);
- }
- return false;
+ static boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isActivityEligibleForOrientationOverride(activity);
}
/**
@@ -116,11 +128,11 @@ class AppCompatCameraPolicy {
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
* </ul>
*/
- boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.isTreatmentEnabledForActivity(activity);
- }
- return false;
+ static boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isTreatmentEnabledForActivity(activity);
}
void start() {
@@ -166,28 +178,93 @@ class AppCompatCameraPolicy {
: SCREEN_ORIENTATION_UNSPECIFIED;
}
+ // TODO(b/369070416): have policies implement the same interface.
+ static boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .shouldCameraCompatControlOrientation(activity))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .shouldCameraCompatControlOrientation(activity));
+ }
+
+ // TODO(b/369070416): have policies implement the same interface.
+ static boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .shouldCameraCompatControlAspectRatio(activity))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .shouldCameraCompatControlAspectRatio(activity));
+ }
+
+ // TODO(b/369070416): have policies implement the same interface.
/**
- * @return {@code true} if the Camera is active for the provided {@link ActivityRecord}.
+ * @return {@code true} if the Camera is active for the provided {@link ActivityRecord} and
+ * any camera compat treatment could be triggered for the current windowing mode.
*/
- boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
- return mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.isCameraActive(activity, mustBeFullscreen);
+ private static boolean isCameraRunningAndWindowingModeEligible(
+ @NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isCameraRunningAndWindowingModeEligible(activity,
+ /* mustBeFullscreen */ true))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .isCameraRunningAndWindowingModeEligible(activity));
}
@Nullable
String getSummaryForDisplayRotationHistoryRecord() {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord();
+ return mDisplayRotationCompatPolicy != null
+ ? mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord()
+ : null;
+ }
+
+ // TODO(b/369070416): have policies implement the same interface.
+ static float getCameraCompatMinAspectRatio(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return 1.0f;
}
- return null;
+ float displayRotationCompatPolicyAspectRatio =
+ cameraPolicy.mDisplayRotationCompatPolicy != null
+ ? cameraPolicy.mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
+ : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ float cameraCompatFreeformPolicyAspectRatio =
+ cameraPolicy.mCameraCompatFreeformPolicy != null
+ ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
+ : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ return Math.max(displayRotationCompatPolicyAspectRatio,
+ cameraCompatFreeformPolicyAspectRatio);
+ }
+
+ @CameraCompatTaskInfo.FreeformCameraCompatMode
+ static int getCameraCompatFreeformMode(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mCameraCompatFreeformPolicy != null
+ ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatMode(activity)
+ : CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
}
/**
* Whether we should apply the min aspect ratio per-app override only when an app is connected
* to the camera.
*/
- boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
- return isCameraActive(activityRecord, /* mustBeFullscreen= */ true)
+ static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
+ return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
&& activityRecord.mAppCompatController.getAppCompatCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
index 42378aaf6c05..9a15c4a8bff2 100644
--- a/services/core/java/com/android/server/wm/AppCompatConfiguration.java
+++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
@@ -290,6 +290,10 @@ final class AppCompatConfiguration {
// is enabled and activity is connected to the camera in fullscreen.
private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
+ // Which aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ // for treatment is connected to the camera.
+ private float mCameraCompatAspectRatio;
+
// Whether activity "refresh" in camera compatibility treatment is enabled.
// See RefreshCallbackItem for context.
private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
@@ -300,6 +304,11 @@ final class AppCompatConfiguration {
// See RefreshCallbackItem for context.
private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+ // Whether camera compat freeform treatment should be enabled for all eligible activities.
+ // This has the same effect as enabling the per-app override
+ // ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT for every app.
+ private boolean mIsCameraCompatFreeformWindowingTreatmentEnabled = false;
+
// Whether should ignore app requested orientation in response to an app
// calling Activity#setRequestedOrientation. See
// LetterboxUiController#shouldIgnoreRequestedOrientation for details.
@@ -363,6 +372,8 @@ final class AppCompatConfiguration {
.config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
mIsCameraCompatSplitScreenAspectRatioEnabled = mContext.getResources().getBoolean(
R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
+ mCameraCompatAspectRatio = mContext.getResources().getFloat(
+ R.dimen.config_windowManagerCameraCompatAspectRatio);
mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
@@ -1320,6 +1331,55 @@ final class AppCompatConfiguration {
}
/**
+ * Overrides aspect ratio to use when camera compat treatment is enabled and an activity
+ * eligible for treatment is connected to the camera.
+ */
+ void setCameraCompatAspectRatio(float aspectRatio) {
+ mCameraCompatAspectRatio = aspectRatio;
+ }
+
+ /**
+ * Which aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ * for treatment is connected to the camera.
+ */
+ float getCameraCompatAspectRatio() {
+ return mCameraCompatAspectRatio;
+ }
+
+ /**
+ * Resets aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ * for treatment is connected to the camera.
+ */
+ void resetCameraCompatAspectRatio() {
+ mCameraCompatAspectRatio = mContext.getResources().getFloat(R.dimen
+ .config_windowManagerCameraCompatAspectRatio);
+ }
+
+ /**
+ * Sets whether the camera compatibility treatment in freeform windowing mode is enabled for
+ * all fixed-orientation apps when using camera.
+ */
+ void setIsCameraCompatFreeformWindowingTreatmentEnabled(boolean enabled) {
+ mIsCameraCompatFreeformWindowingTreatmentEnabled = enabled;
+ }
+
+ /**
+ * Whether the camera compatibility treatment in freeform windowing mode is enabled for all
+ * fixed-orientation apps when using camera.
+ */
+ boolean isCameraCompatFreeformWindowingTreatmentEnabled() {
+ return mIsCameraCompatFreeformWindowingTreatmentEnabled;
+ }
+
+ /**
+ * Resets whether the camera compatibility treatment in freeform windowing mode is enabled for
+ * all fixed-orientation apps when using camera.
+ */
+ void resetIsCameraCompatFreeformWindowingTreatmentEnabled() {
+ mIsCameraCompatFreeformWindowingTreatmentEnabled = false;
+ }
+
+ /**
* Checks whether rotation compat policy for immersive apps that prevents auto rotation
* into non-optimal screen orientation while in fullscreen is enabled at build time. This is
* used when we need to safely initialize a component before the {@link DeviceConfig} flag
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 173362c16728..330283fb9ab3 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -15,13 +15,15 @@
*/
package com.android.server.wm;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY;
+
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
/**
* Allows the interaction with all the app compat policies and configurations
@@ -48,6 +50,8 @@ class AppCompatController {
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
@NonNull
private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
+ @NonNull
+ final BooleanSupplier mAllowRestrictedResizability;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -71,6 +75,31 @@ class AppCompatController {
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
mAppCompatOverrides);
+ mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+ // Application level.
+ if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) {
+ return true;
+ }
+ // Activity level.
+ try {
+ return packageManager.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ mActivityRecord.mActivityComponent.getPackageName(),
+ mActivityRecord.mActivityComponent.getClassName(),
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ });
+ }
+
+ static boolean allowRestrictedResizability(PackageManager pm, String packageName) {
+ try {
+ return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
}
@NonNull
@@ -118,14 +147,6 @@ class AppCompatController {
return mAppCompatOverrides.getAppCompatResizeOverrides();
}
- @Nullable
- AppCompatCameraPolicy getAppCompatCameraPolicy() {
- if (mActivityRecord.mDisplayContent != null) {
- return mActivityRecord.mDisplayContent.mAppCompatCameraPolicy;
- }
- return null;
- }
-
@NonNull
AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
return mAppCompatReachabilityPolicy;
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index afc6506f9091..4e390df14131 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -22,6 +22,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
+import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxInnerBounds;
+import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxOuterBounds;
+import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxPosition;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,6 +35,7 @@ import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -43,7 +47,7 @@ class AppCompatLetterboxPolicy {
@NonNull
private final ActivityRecord mActivityRecord;
@NonNull
- private final LetterboxPolicyState mLetterboxPolicyState;
+ private final AppCompatLetterboxPolicyState mLetterboxPolicyState;
@NonNull
private final AppCompatRoundedCorners mAppCompatRoundedCorners;
@NonNull
@@ -54,7 +58,8 @@ class AppCompatLetterboxPolicy {
AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration) {
mActivityRecord = activityRecord;
- mLetterboxPolicyState = new LetterboxPolicyState();
+ mLetterboxPolicyState = Flags.appCompatRefactoring() ? new ShellLetterboxPolicyState()
+ : new LegacyLetterboxPolicyState();
// TODO (b/358334569) Improve cutout logic dependency on app compat.
mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord,
this::isLetterboxedNotForDisplayCutout);
@@ -88,7 +93,24 @@ class AppCompatLetterboxPolicy {
@Nullable
LetterboxDetails getLetterboxDetails() {
- return mLetterboxPolicyState.getLetterboxDetails();
+ final WindowState w = mActivityRecord.findMainWindow();
+ if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) {
+ return null;
+ }
+ final Rect letterboxInnerBounds = new Rect();
+ final Rect letterboxOuterBounds = new Rect();
+ mLetterboxPolicyState.getLetterboxInnerBounds(letterboxInnerBounds);
+ mLetterboxPolicyState.getLetterboxOuterBounds(letterboxOuterBounds);
+
+ if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
+ return null;
+ }
+
+ return new LetterboxDetails(
+ letterboxInnerBounds,
+ letterboxOuterBounds,
+ w.mAttrs.insetsFlags.appearance
+ );
}
/**
@@ -99,6 +121,13 @@ class AppCompatLetterboxPolicy {
return mLetterboxPolicyState.isFullyTransparentBarAllowed(rect);
}
+ /**
+ * Updates the letterbox surfaces in case this is needed.
+ *
+ * @param winHint The WindowState for the letterboxed Activity.
+ * @param t The current Transaction.
+ * @param inputT The pending transaction used for the input surface.
+ */
void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
@NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction inputT) {
@@ -232,12 +261,17 @@ class AppCompatLetterboxPolicy {
|| w.mAnimatingExit;
}
- private class LetterboxPolicyState {
+ /**
+ * Existing {@link AppCompatLetterboxPolicyState} implementation.
+ * TODO(b/375339716): Clean code for legacy implementation.
+ */
+ private class LegacyLetterboxPolicyState implements AppCompatLetterboxPolicyState {
@Nullable
private Letterbox mLetterbox;
- void layoutLetterboxIfNeeded(@NonNull WindowState w) {
+ @Override
+ public void layoutLetterboxIfNeeded(@NonNull WindowState w) {
if (!isRunning()) {
final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
.mAppCompatController.getAppCompatLetterboxOverrides();
@@ -252,41 +286,11 @@ class AppCompatLetterboxPolicy {
.setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
}
final Point letterboxPosition = new Point();
- if (mActivityRecord.isInLetterboxAnimation()) {
- // In this case we attach the letterbox to the task instead of the activity.
- mActivityRecord.getTask().getPosition(letterboxPosition);
- } else {
- mActivityRecord.getPosition(letterboxPosition);
- }
-
- // Get the bounds of the "space-to-fill". The transformed bounds have the highest
- // priority because the activity is launched in a rotated environment. In multi-window
- // mode, the taskFragment-level represents this for both split-screen
- // and activity-embedding. In fullscreen-mode, the task container does
- // (since the orientation letterbox is also applied to the task).
- final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
- final Rect spaceToFill = transformedBounds != null
- ? transformedBounds
- : mActivityRecord.inMultiWindowMode()
- ? mActivityRecord.getTaskFragment().getBounds()
- : mActivityRecord.getRootTask().getParent().getBounds();
- // In case of translucent activities an option is to use the WindowState#getFrame() of
- // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
- // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct
- // information and in particular it might provide a value for a smaller area making
- // the letterbox overlap with the translucent activity's frame.
- // If we use WindowState#getFrame() for the translucent activity's letterbox inner
- // frame, the letterbox will then be overlapped with the translucent activity's frame.
- // Because the surface layer of letterbox is lower than an activity window, this
- // won't crop the content, but it may affect other features that rely on values stored
- // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher
- // For this reason we use ActivityRecord#getBounds() that the translucent activity
- // inherits from the first opaque activity beneath and also takes care of the scaling
- // in case of activities in size compat mode.
- final TransparentPolicy transparentPolicy =
- mActivityRecord.mAppCompatController.getTransparentPolicy();
- final Rect innerFrame =
- transparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame();
+ calculateLetterboxPosition(mActivityRecord, letterboxPosition);
+ final Rect spaceToFill = new Rect();
+ calculateLetterboxOuterBounds(mActivityRecord, spaceToFill);
+ final Rect innerFrame = new Rect();
+ calculateLetterboxInnerBounds(mActivityRecord, w, innerFrame);
mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition);
if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
.isDoubleTapEvent()) {
@@ -299,18 +303,21 @@ class AppCompatLetterboxPolicy {
* @return {@code true} if the policy is running and so if the current activity is
* letterboxed.
*/
- boolean isRunning() {
+ @Override
+ public boolean isRunning() {
return mLetterbox != null;
}
- void onMovedToDisplay(int displayId) {
+ @Override
+ public void onMovedToDisplay(int displayId) {
if (isRunning()) {
mLetterbox.onMovedToDisplay(displayId);
}
}
/** Cleans up {@link Letterbox} if it exists.*/
- void stop() {
+ @Override
+ public void stop() {
if (isRunning()) {
mLetterbox.destroy();
mLetterbox = null;
@@ -319,7 +326,8 @@ class AppCompatLetterboxPolicy {
.setLetterboxInnerBoundsSupplier(null);
}
- void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
+ @Override
+ public void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
@NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction inputT) {
if (shouldNotLayoutLetterbox(winHint)) {
@@ -331,15 +339,17 @@ class AppCompatLetterboxPolicy {
}
}
- void hide() {
+ @Override
+ public void hide() {
if (isRunning()) {
mLetterbox.hide();
}
}
/** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
+ @Override
@NonNull
- Rect getLetterboxInsets() {
+ public Rect getLetterboxInsets() {
if (isRunning()) {
return mLetterbox.getInsets();
} else {
@@ -348,7 +358,8 @@ class AppCompatLetterboxPolicy {
}
/** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */
- void getLetterboxInnerBounds(@NonNull Rect outBounds) {
+ @Override
+ public void getLetterboxInnerBounds(@NonNull Rect outBounds) {
if (isRunning()) {
outBounds.set(mLetterbox.getInnerFrame());
final WindowState w = mActivityRecord.findMainWindow();
@@ -361,7 +372,8 @@ class AppCompatLetterboxPolicy {
}
/** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */
- private void getLetterboxOuterBounds(@NonNull Rect outBounds) {
+ @Override
+ public void getLetterboxOuterBounds(@NonNull Rect outBounds) {
if (isRunning()) {
outBounds.set(mLetterbox.getOuterFrame());
} else {
@@ -373,39 +385,130 @@ class AppCompatLetterboxPolicy {
* @return {@code true} if bar shown within a given rectangle is allowed to be fully
* transparent when the current activity is displayed.
*/
- boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
+ @Override
+ public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect);
}
@Nullable
- LetterboxDetails getLetterboxDetails() {
- final WindowState w = mActivityRecord.findMainWindow();
- if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) {
- return null;
+ private SurfaceControl getLetterboxParentSurface() {
+ if (mActivityRecord.isInLetterboxAnimation()) {
+ return mActivityRecord.getTask().getSurfaceControl();
+ }
+ return mActivityRecord.getSurfaceControl();
+ }
+
+ }
+
+ /**
+ * {@link AppCompatLetterboxPolicyState} implementation for the letterbox presentation on shell.
+ */
+ private class ShellLetterboxPolicyState implements AppCompatLetterboxPolicyState {
+
+ private final Rect mInnerBounds = new Rect();
+ private final Rect mOuterBounds = new Rect();
+ private final Point mLetterboxPosition = new Point();
+ private boolean mRunning;
+
+ @Override
+ public void layoutLetterboxIfNeeded(@NonNull WindowState w) {
+ mRunning = true;
+ calculateLetterboxPosition(mActivityRecord, mLetterboxPosition);
+ calculateLetterboxOuterBounds(mActivityRecord, mOuterBounds);
+ calculateLetterboxInnerBounds(mActivityRecord, w, mInnerBounds);
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ .setLetterboxInnerBoundsSupplier(() -> mInnerBounds);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @Override
+ public void onMovedToDisplay(int displayId) {
+ // TODO(b/374918469): Handle Display Change for Letterbox in Shell
+ }
+
+ @Override
+ public void stop() {
+ if (!isRunning()) {
+ return;
+ }
+ mRunning = false;
+ mLetterboxPosition.set(0, 0);
+ mInnerBounds.setEmpty();
+ mOuterBounds.setEmpty();
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ .setLetterboxInnerBoundsSupplier(null);
+ }
+
+ @Override
+ public void hide() {
+ if (!isRunning()) {
+ return;
}
- final Rect letterboxInnerBounds = new Rect();
- final Rect letterboxOuterBounds = new Rect();
- getLetterboxInnerBounds(letterboxInnerBounds);
- getLetterboxOuterBounds(letterboxOuterBounds);
+ mLetterboxPosition.set(0, 0);
+ mInnerBounds.setEmpty();
+ mOuterBounds.setEmpty();
+ }
- if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
- return null;
+ @NonNull
+ @Override
+ public Rect getLetterboxInsets() {
+ if (isRunning()) {
+ return new Rect(
+ Math.max(0, mInnerBounds.left - mOuterBounds.left),
+ Math.max(0, mOuterBounds.top - mInnerBounds.top),
+ Math.max(0, mOuterBounds.right - mInnerBounds.right),
+ Math.max(0, mInnerBounds.bottom - mOuterBounds.bottom)
+ );
}
+ return new Rect();
+ }
- return new LetterboxDetails(
- letterboxInnerBounds,
- letterboxOuterBounds,
- w.mAttrs.insetsFlags.appearance
- );
+ @Override
+ public void getLetterboxInnerBounds(@NonNull Rect outBounds) {
+ if (isRunning()) {
+ outBounds.set(mInnerBounds);
+ final WindowState w = mActivityRecord.findMainWindow();
+ if (w != null) {
+ AppCompatUtils.adjustBoundsForTaskbar(w, outBounds);
+ }
+ } else {
+ outBounds.setEmpty();
+ }
}
- @Nullable
- private SurfaceControl getLetterboxParentSurface() {
- if (mActivityRecord.isInLetterboxAnimation()) {
- return mActivityRecord.getTask().getSurfaceControl();
+ @Override
+ public void getLetterboxOuterBounds(@NonNull Rect outBounds) {
+ if (isRunning()) {
+ outBounds.set(mOuterBounds);
+ } else {
+ outBounds.setEmpty();
}
- return mActivityRecord.getSurfaceControl();
}
+ @Override
+ public void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
+ @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction inputT) {
+
+ if (shouldNotLayoutLetterbox(winHint)) {
+ return;
+ }
+ start(winHint);
+ }
+
+ @Override
+ public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
+ // TODO(b/374921442) Handle Transparent Activities Letterboxing in Shell.
+ // At the moment Shell handles letterbox with a single surface. This would make
+ // notIntersectsOrFullyContains() to return false in the existing Letterbox
+ // implementation.
+ // Note: Previous implementation is
+ // !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect);
+ return !isRunning();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java
new file mode 100644
index 000000000000..31ad536c2eb2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+/**
+ * Abstraction for different Letterbox state implementations.
+ */
+interface AppCompatLetterboxPolicyState {
+
+ /**
+ * Checks if a relayout is necessary for the letterbox implementations.
+ * @param w The {@link WindowState} to use for defining Letterbox sizes.
+ */
+ void layoutLetterboxIfNeeded(@NonNull WindowState w);
+
+ /**
+ * @return {@code true} if the policy is running and so if the current activity is
+ * letterboxed.
+ */
+ boolean isRunning();
+
+ /**
+ * Called when the activity is moved to a new display.
+ * @param displayId Id for the new display
+ */
+ void onMovedToDisplay(int displayId);
+
+ /** Cleans up {@link Letterbox} if it exists.*/
+ void stop();
+
+ /** Hides the letterbox surfaces implementation. */
+ void hide();
+
+ /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
+ @NonNull
+ Rect getLetterboxInsets();
+
+ /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */
+ void getLetterboxInnerBounds(@NonNull Rect outBounds);
+
+ /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */
+ void getLetterboxOuterBounds(@NonNull Rect outBounds);
+
+ /**
+ * Updates the letterbox surfaces in case this is needed.
+ *
+ * @param winHint The WindowState for the letterboxed Activity.
+ * @param t The current Transaction.
+ * @param inputT The pending transaction used for the input surface.
+ */
+ void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint,
+ @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction inputT);
+
+ /**
+ * @return {@code true} if bar shown within a given rectangle is allowed to be fully
+ * transparent when the current activity is displayed.
+ */
+ boolean isFullyTransparentBarAllowed(@NonNull Rect rect);
+
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
new file mode 100644
index 000000000000..79b3a55d0463
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Some utility methods used by different Letterbox implementations.
+ */
+class AppCompatLetterboxUtils {
+ /**
+ * Provides the position of the top left letterbox area in the display coordinate system.
+ *
+ * @param activity The Letterboxed activity.
+ * @param outLetterboxPosition InOut parameter that will contain the desired letterbox position.
+ */
+ static void calculateLetterboxPosition(@NonNull ActivityRecord activity,
+ @NonNull Point outLetterboxPosition) {
+ if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) {
+ outLetterboxPosition.set(0, 0);
+ return;
+ }
+ if (activity.isInLetterboxAnimation()) {
+ // In this case we attach the letterbox to the task instead of the activity.
+ activity.getTask().getPosition(outLetterboxPosition);
+ } else {
+ activity.getPosition(outLetterboxPosition);
+ }
+ }
+
+ /**
+ * Provides all the available space, in display coordinate, to fill with the letterboxed
+ * activity and the letterbox areas.
+ *
+ * @param activity The Letterboxed activity.
+ * @param outOuterBounds InOut parameter that will contain the outer bounds for the letterboxed
+ * activity.
+ */
+ static void calculateLetterboxOuterBounds(@NonNull ActivityRecord activity,
+ @NonNull Rect outOuterBounds) {
+ if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) {
+ outOuterBounds.setEmpty();
+ return;
+ }
+ // Get the bounds of the "space-to-fill". The transformed bounds have the highest
+ // priority because the activity is launched in a rotated environment. In multi-window
+ // mode, the taskFragment-level represents this for both split-screen
+ // and activity-embedding. In fullscreen-mode, the task container does
+ // (since the orientation letterbox is also applied to the task).
+ final Rect transformedBounds =
+ activity.getFixedRotationTransformDisplayBounds();
+ outOuterBounds.set(transformedBounds != null
+ ? transformedBounds
+ : activity.inMultiWindowMode()
+ ? activity.getTaskFragment().getBounds()
+ : activity.getRootTask().getParent().getBounds());
+ }
+
+ /**
+ * Provides the inner bounds for the letterboxed activity in display coordinates. This is the
+ * space the letterboxed activity will use.
+ *
+ * @param activity The Letterboxed activity.
+ * @param outInnerBounds InOut parameter that will contain the inner bounds for the letterboxed
+ * activity.
+ */
+ static void calculateLetterboxInnerBounds(@NonNull ActivityRecord activity,
+ @NonNull WindowState window, @NonNull Rect outInnerBounds) {
+ if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) {
+ outInnerBounds.setEmpty();
+ return;
+ }
+ // In case of translucent activities an option is to use the WindowState#getFrame() of
+ // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
+ // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct
+ // information and in particular it might provide a value for a smaller area making
+ // the letterbox overlap with the translucent activity's frame.
+ // If we use WindowState#getFrame() for the translucent activity's letterbox inner
+ // frame, the letterbox will then be overlapped with the translucent activity's frame.
+ // Because the surface layer of letterbox is lower than an activity window, this
+ // won't crop the content, but it may affect other features that rely on values stored
+ // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher
+ // For this reason we use ActivityRecord#getBounds() that the translucent activity
+ // inherits from the first opaque activity beneath and also takes care of the scaling
+ // in case of activities in size compat mode.
+ final TransparentPolicy transparentPolicy =
+ activity.mAppCompatController.getTransparentPolicy();
+ outInnerBounds.set(
+ transparentPolicy.isRunning() ? activity.getBounds() : window.getFrame());
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index bd01351251a5..c84711d4be51 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -106,6 +107,16 @@ class AppCompatOrientationOverrides {
return isChangeEnabled(mActivityRecord, OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
}
+ boolean shouldRespectRequestedOrientationDueToOverride() {
+ // Checking TaskFragment rather than ActivityRecord to ensure that transition
+ // between fullscreen and PiP would work well. Checking TaskFragment rather than
+ // Task to ensure that Activity Embedding is excluded.
+ return mActivityRecord.isVisibleRequested() && mActivityRecord.getTaskFragment() != null
+ && mActivityRecord.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+ .isOverrideRespectRequestedOrientationEnabled();
+ }
+
/**
* Whether an app is calling {@link android.app.Activity#setRequestedOrientation}
* in a loop and orientation request should be ignored.
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 7477c6272d89..7aed33d94223 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -53,33 +53,36 @@ class AppCompatOrientationPolicy {
@ActivityInfo.ScreenOrientation
int overrideOrientationIfNeeded(@ActivityInfo.ScreenOrientation int candidate) {
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ // Ignore all orientation requests of activities for eligible virtual displays.
+ if (aspectRatioOverrides.shouldIgnoreActivitySizeRestrictionsForDisplay()) {
+ return SCREEN_ORIENTATION_USER;
+ }
final DisplayContent displayContent = mActivityRecord.mDisplayContent;
final boolean isIgnoreOrientationRequestEnabled = displayContent != null
&& displayContent.getIgnoreOrientationRequest();
- final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
- .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
- final boolean isCameraActive = displayContent != null
- && displayContent.mAppCompatCameraPolicy.isCameraActive(mActivityRecord,
- /* mustBeFullscreen */ true);
- if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
+ final boolean hasFullscreenOverride = aspectRatioOverrides.hasFullscreenOverride();
+ final boolean shouldCameraCompatControlOrientation =
+ AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
+ if (hasFullscreenOverride && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
// often results in sideways or stretched previews. As the camera compat treatment
// targets fixed-orientation activities, overriding the orientation disables the
// treatment.
- && !isCameraActive) {
+ && !shouldCameraCompatControlOrientation) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
+ " for " + mActivityRecord + " is overridden to "
- + screenOrientationToString(SCREEN_ORIENTATION_USER)
- + " by user aspect ratio settings.");
+ + screenOrientationToString(SCREEN_ORIENTATION_USER));
return SCREEN_ORIENTATION_USER;
}
// In some cases (e.g. Kids app) we need to map the candidate orientation to some other
// orientation.
candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
- final boolean shouldApplyUserMinAspectRatioOverride = mAppCompatOverrides
- .getAppCompatAspectRatioOverrides().shouldApplyUserMinAspectRatioOverride();
+ final boolean shouldApplyUserMinAspectRatioOverride = aspectRatioOverrides
+ .shouldApplyUserMinAspectRatioOverride();
if (shouldApplyUserMinAspectRatioOverride && (!isFixedOrientation(candidate)
|| candidate == SCREEN_ORIENTATION_LOCKED)) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
@@ -97,29 +100,11 @@ class AppCompatOrientationPolicy {
if (displayContent != null
&& mAppCompatOverrides.getAppCompatCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
- && !displayContent.mAppCompatCameraPolicy
+ && !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
return candidate;
}
- // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
- // which will always come first before this check as user override > device
- // manufacturer override.
- final boolean isSystemOverrideToFullscreenEnabled = mAppCompatOverrides
- .getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
- if (isSystemOverrideToFullscreenEnabled && isIgnoreOrientationRequestEnabled
- // Do not override orientation to fullscreen for camera activities.
- // Fixed-orientation activities are rarely tested in other orientations, and it
- // often results in sideways or stretched previews. As the camera compat treatment
- // targets fixed-orientation activities, overriding the orientation disables the
- // treatment.
- && !isCameraActive) {
- Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
- + " for " + mActivityRecord + " is overridden to "
- + screenOrientationToString(SCREEN_ORIENTATION_USER));
- return SCREEN_ORIENTATION_USER;
- }
-
final AppCompatOrientationOverrides.OrientationOverridesState capabilityState =
mAppCompatOverrides.getAppCompatOrientationOverrides()
.mOrientationOverridesState;
@@ -192,8 +177,9 @@ class AppCompatOrientationPolicy {
+ mActivityRecord);
return true;
}
- final AppCompatCameraPolicy cameraPolicy = mActivityRecord.mAppCompatController
- .getAppCompatCameraPolicy();
+
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
+ .getAppCompatCameraPolicy(mActivityRecord);
if (cameraPolicy != null
&& cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) {
Slog.w(TAG, "Ignoring orientation update to "
@@ -211,5 +197,4 @@ class AppCompatOrientationPolicy {
}
return false;
}
-
}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index f069dcdbc86b..d0d3d4321a0a 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -149,7 +149,7 @@ class AppCompatSizeCompatModePolicy {
@NonNull Configuration newParentConfig) {
mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
.findOpaqueNotFinishingActivityBelow()
- .map(activityRecord -> mSizeCompatScale)
+ .map(ar -> Math.min(1.0f, ar.getCompatScale()))
.orElseGet(() -> calculateSizeCompatScale(
resolvedAppBounds, containerAppBounds, newParentConfig));
}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 69421d0d5c96..a41832498880 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -32,6 +32,8 @@ import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.WindowInsets;
+import com.android.window.flags.Flags;
+
import java.util.function.BooleanSupplier;
/**
@@ -86,10 +88,22 @@ final class AppCompatUtils {
/**
* @param activityRecord The {@link ActivityRecord} for the app package.
* @param overrideChangeId The per-app override identifier.
- * @return {@code true} if the per-app override is enable for the given activity.
+ * @return {@code true} if the per-app override is enable for the given activity and the
+ * display does not ignore fixed orientation, aspect ratio and resizability of activity.
*/
static boolean isChangeEnabled(@NonNull ActivityRecord activityRecord, long overrideChangeId) {
- return activityRecord.info.isChangeEnabled(overrideChangeId);
+ return activityRecord.info.isChangeEnabled(overrideChangeId)
+ && !isDisplayIgnoreActivitySizeRestrictions(activityRecord);
+ }
+
+ /**
+ * Whether the display ignores fixed orientation, aspect ratio and resizability of activities.
+ */
+ static boolean isDisplayIgnoreActivitySizeRestrictions(
+ @NonNull ActivityRecord activityRecord) {
+ final DisplayContent dc = activityRecord.mDisplayContent;
+ return Flags.vdmForceAppUniversalResizableApi() && dc != null
+ && dc.isDisplayIgnoreActivitySizeRestrictions();
}
/**
@@ -150,43 +164,49 @@ final class AppCompatUtils {
appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
- final Rect bounds = top.getBounds();
- final Rect appBounds = getAppBounds(top);
- appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
- appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
- appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
- appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
-
- // We need to consider if letterboxed or pillarboxed.
- // TODO(b/336807329) Encapsulate reachability logic
- appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
- .isLetterboxDoubleTapEducationEnabled());
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
- if (appCompatTaskInfo.isTopActivityPillarboxed()) {
- if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
- // Pillarboxed.
- appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
- reachabilityOverrides.getLetterboxPositionForHorizontalReachability();
- } else {
- appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
- }
- } else {
- if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
- // Letterboxed.
- appCompatTaskInfo.topActivityLetterboxVerticalPosition =
- reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+ final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed();
+ appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed);
+ if (isTopActivityLetterboxed) {
+ final Rect bounds = top.getBounds();
+ final Rect appBounds = getAppBounds(top);
+ appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
+ appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
+ appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
+ appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
+ // TODO(b/379824541) Remove duplicate information.
+ appCompatTaskInfo.topActivityLetterboxBounds = bounds;
+ // We need to consider if letterboxed or pillarboxed.
+ // TODO(b/336807329) Encapsulate reachability logic
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
+ .isLetterboxDoubleTapEducationEnabled());
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
+ if (appCompatTaskInfo.isTopActivityPillarboxShaped()) {
+ if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
+ // Pillarboxed.
+ appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
+ reachabilityOverrides
+ .getLetterboxPositionForHorizontalReachability();
+ } else {
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+ }
} else {
- appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+ if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
+ // Letterboxed.
+ appCompatTaskInfo.topActivityLetterboxVerticalPosition =
+ reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+ } else {
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+ }
}
}
}
+
final boolean eligibleForAspectRatioButton =
!info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat()
&& aspectRatioOverrides.shouldEnableUserAspectRatioSettings();
appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton);
- appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
- appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController
- .getAppCompatCameraOverrides().getFreeformCameraCompatMode();
+ appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
+ AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
.getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task));
}
@@ -263,6 +283,7 @@ final class AppCompatUtils {
info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxBounds = null;
info.cameraCompatTaskInfo.freeformCameraCompatMode =
CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
info.clearTopActivityFlags();
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index d699af872c7f..c845c5089f23 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -17,12 +17,11 @@
package com.android.server.wm;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
-import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import android.app.ActivityManager;
-import android.app.BackgroundStartPrivileges;
import android.app.IAppTask;
import android.app.IApplicationThread;
import android.content.Intent;
@@ -136,7 +135,7 @@ class AppTaskImpl extends IAppTask.Stub {
-1,
callerApp,
null,
- BackgroundStartPrivileges.NONE,
+ false,
null,
null,
null);
@@ -160,6 +159,7 @@ class AppTaskImpl extends IAppTask.Stub {
Intent intent, String resolvedType, Bundle bOptions) {
checkCallerOrSystemOrRoot();
mService.assertPackageMatchesCallingUid(callingPackage);
+ mService.mAmInternal.addCreatorToken(intent, callingPackage);
int callingUser = UserHandle.getCallingUserId();
Task task;
@@ -175,13 +175,14 @@ class AppTaskImpl extends IAppTask.Stub {
throw new IllegalArgumentException("Bad app thread " + appThread);
}
}
-
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
return mService.getActivityStartController().obtainStarter(intent, "AppTaskImpl")
.setCaller(appThread)
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
.setResolvedType(resolvedType)
- .setActivityOptions(bOptions)
+ .setActivityOptions(bOptions, callingPid, callingUid)
.setUserId(callingUser)
.setInTask(task)
.execute();
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 90d33fbef748..932f26857105 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -88,9 +88,9 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe
import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index ab02d49e5f96..741eefae4462 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -57,8 +57,8 @@ import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index fcaab2c0f8c7..601b17c46c03 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Build;
@@ -40,6 +41,8 @@ import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -76,6 +79,7 @@ class AppWarnings {
public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
+ public static final int FLAG_HIDE_PAGE_SIZE_MISMATCH = 0x10;
/**
* Map of package flags for each user.
@@ -101,6 +105,7 @@ class AppWarnings {
private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs;
private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs;
private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs;
+ private SparseArray<PageSizeMismatchDialog> mPageSizeMismatchDialogs;
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
@@ -250,6 +255,19 @@ class AppWarnings {
}
}
+ public void showPageSizeMismatchDialogIfNeeded(ActivityRecord r) {
+ // Don't show dialog if the app compat is enabled using property
+ final boolean appCompatEnabled = SystemProperties.getBoolean(
+ "bionic.linker.16kb.app_compat.enabled", false);
+ if (appCompatEnabled) {
+ return;
+ }
+ boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
+ if (is16KbDevice) {
+ mUiHandler.showPageSizeMismatchDialog(r);
+ }
+ }
+
/**
* Called when an activity is being started.
*
@@ -260,6 +278,9 @@ class AppWarnings {
showUnsupportedDisplaySizeDialogIfNeeded(r);
showDeprecatedTargetDialogIfNeeded(r);
showDeprecatedAbiDialogIfNeeded(r);
+ if (Flags.appCompatOption16kb()) {
+ showPageSizeMismatchDialogIfNeeded(r);
+ }
}
/**
@@ -457,6 +478,41 @@ class AppWarnings {
}
}
+ @UiThread
+ private void showPageSizeMismatchDialogUiThread(@NonNull ActivityRecord ar) {
+ String warning =
+ mAtm.mContext
+ .getPackageManager()
+ .getPageSizeCompatWarningMessage(ar.info.packageName);
+ if (warning == null) {
+ return;
+ }
+
+ final int userId = getUserIdForActivity(ar);
+ PageSizeMismatchDialog pageSizeMismatchDialog;
+ if (mPageSizeMismatchDialogs != null) {
+ pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
+ if (pageSizeMismatchDialog != null) {
+ pageSizeMismatchDialog.dismiss();
+ mPageSizeMismatchDialogs.remove(userId);
+ }
+ }
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) {
+ pageSizeMismatchDialog =
+ new PageSizeMismatchDialog(
+ AppWarnings.this,
+ getUiContextForActivity(ar),
+ ar.info.applicationInfo,
+ userId,
+ warning);
+ pageSizeMismatchDialog.show();
+ if (mPageSizeMismatchDialogs == null) {
+ mPageSizeMismatchDialogs = new SparseArray<>();
+ }
+ mPageSizeMismatchDialogs.put(userId, pageSizeMismatchDialog);
+ }
+ }
+
/**
* Dismisses all warnings for the given package.
* <p>
@@ -510,6 +566,16 @@ class AppWarnings {
mDeprecatedAbiDialogs.remove(userId);
}
}
+
+ // Hides the "page size app compat" dialog if necessary.
+ if (mPageSizeMismatchDialogs != null) {
+ PageSizeMismatchDialog pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
+ if (pageSizeMismatchDialog != null
+ && (name == null || name.equals(pageSizeMismatchDialog.mPackageName))) {
+ pageSizeMismatchDialog.dismiss();
+ mPageSizeMismatchDialogs.remove(userId);
+ }
+ }
}
/**
@@ -649,6 +715,7 @@ class AppWarnings {
private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6;
+ private static final int MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG = 7;
public UiHandler(Looper looper) {
super(looper, null, true);
@@ -681,6 +748,10 @@ class AppWarnings {
final ActivityRecord ar = (ActivityRecord) msg.obj;
showDeprecatedAbiDialogUiThread(ar);
} break;
+ case MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showPageSizeMismatchDialogUiThread(ar);
+ } break;
}
}
@@ -712,6 +783,11 @@ class AppWarnings {
public void hideDialogsForPackage(String name, int userId) {
obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget();
}
+
+ public void showPageSizeMismatchDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG);
+ obtainMessage(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG, r).sendToTarget();
+ }
}
static class BaseDialog {
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index f0a6e9ec1d4f..dd1af0a497ca 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -653,7 +653,9 @@ class AsyncRotationController extends FadeAnimationController implements Consume
// by drawing the rotated content before applying projection transaction of display.
// And it will fade in after the display transition is finished.
if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
- && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) {
+ && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()
+ && !mService.mAtmService.mBackNavigationController.hasFixedRotationAnimation(
+ mDisplayContent)) {
hideImmediately(w.mToken, Operation.ACTION_FADE);
if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 4554b210e3fd..7deb6a8232be 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index dd86a149d0a5..3968b525f11d 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,14 +26,17 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
import static com.android.server.wm.BackNavigationProto.ANIMATION_RUNNING;
import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE;
import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY;
import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
+import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +62,7 @@ import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IWindowlessStartingSurfaceCallback;
import android.window.OnBackInvokedCallbackInfo;
+import android.window.SystemOverrideOnBackInvokedCallback;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
@@ -106,6 +110,12 @@ class BackNavigationController {
mNavigationMonitor.onFocusWindowChanged(newFocus);
}
+ void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
+ if (Flags.disallowAppProgressEmbeddedWindow()) {
+ mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
+ }
+ }
+
/**
* Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
* back gesture animation.
@@ -177,6 +187,9 @@ class BackNavigationController {
return null;
}
+ final ArrayList<EmbeddedWindowController.EmbeddedWindow> embeddedWindows = wmService
+ .mEmbeddedWindowController.getByHostWindow(window);
+
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
if ((currentTask != null && !currentTask.isVisibleRequested())
@@ -198,14 +211,37 @@ class BackNavigationController {
infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback());
infoBuilder.setTouchableRegion(window.getFrame());
- infoBuilder.setAppProgressAllowed((window.getAttrs().privateFlags
- & PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED) != 0);
+ if (currentTask != null) {
+ infoBuilder.setFocusedTaskId(currentTask.mTaskId);
+ }
+ boolean transferGestureToEmbedded = false;
+ if (Flags.disallowAppProgressEmbeddedWindow() && embeddedWindows != null) {
+ for (int i = embeddedWindows.size() - 1; i >= 0; --i) {
+ if (embeddedWindows.get(i).mGestureToEmbedded) {
+ transferGestureToEmbedded = true;
+ break;
+ }
+ }
+ }
+ final boolean canInterruptInView = (window.getAttrs().privateFlags
+ & PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED) != 0;
+ infoBuilder.setAppProgressAllowed(canInterruptInView && !transferGestureToEmbedded
+ && callbackInfo.isAnimationCallback());
mNavigationMonitor.startMonitor(window, navigationObserver);
+ int requestOverride = callbackInfo.getOverrideBehavior();
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
+ "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
currentTask, currentActivity, callbackInfo, window);
-
+ if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) {
+ final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity()
+ : null;
+ if (currentActivity != null && rootR != currentActivity) {
+ // The top activity is not root activity, the activity cannot remove task when
+ // finishAndRemoveTask called.
+ requestOverride = OVERRIDE_UNDEFINED;
+ }
+ }
// Clear the pointer down outside focus if any.
mWindowManagerService.clearPointerDownOutsideFocusRunnable();
@@ -250,7 +286,8 @@ class BackNavigationController {
} else if (hasTranslucentActivity(currentActivity, prevActivities)) {
// skip if one of participant activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
- } else if (prevActivities.size() > 0) {
+ } else if (prevActivities.size() > 0
+ && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
&& isAllActivitiesCreated(prevActivities)) {
// We have another Activity in the same currentTask to go to
@@ -585,6 +622,15 @@ class BackNavigationController {
}
}
+ boolean hasFixedRotationAnimation(@NonNull DisplayContent displayContent) {
+ if (!mAnimationHandler.mComposed) {
+ return false;
+ }
+ final ActivityRecord openActivity = mAnimationHandler.mOpenActivities[0];
+ return displayContent == openActivity.mDisplayContent
+ && displayContent.isFixedRotationLaunchingApp(openActivity);
+ }
+
private boolean isWaitBackTransition() {
// Ignore mWaitTransition while flag is enabled.
return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition()
@@ -738,6 +784,20 @@ class BackNavigationController {
}
/**
+ * Notify focus window has transferred touch gesture to embedded window. Shell should pilfer
+ * pointers so embedded process won't receive motion event.
+ *
+ */
+ void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
+ if (!isMonitorForRemote() || host != mNavigatingWindow) {
+ return;
+ }
+ final Bundle result = new Bundle();
+ result.putBoolean(BackNavigationInfo.KEY_TOUCH_GESTURE_TRANSFERRED, true);
+ mObserver.sendResult(result);
+ }
+
+ /**
* Notify an unexpected transition has happened during back navigation.
*/
private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening,
@@ -987,10 +1047,18 @@ class BackNavigationController {
return;
}
+ if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
+ Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+ cancelPendingAnimation();
+ return;
+ }
+
// Ensure the final animation targets which hidden by transition could be visible.
for (int i = 0; i < targets.size(); i++) {
final WindowContainer wc = targets.get(i).mContainer;
- wc.prepareSurfaces();
+ if (wc.mSurfaceControl != null) {
+ wc.prepareSurfaces();
+ }
}
// The pending builder could be cleared due to prepareSurfaces
@@ -1013,6 +1081,7 @@ class BackNavigationController {
Slog.e(TAG, "Remote animation gone", e);
}
mPendingAnimationBuilder = null;
+ mNavigationMonitor.stopMonitorTransition();
}
/**
@@ -1237,8 +1306,9 @@ class BackNavigationController {
}
allWindowDrawn &= next.mAppWindowDrawn;
}
- // Do not remove until transition ready.
- if (!activity.isVisible()) {
+ // Do not remove windowless surfaces if the transaction has not been applied.
+ if (activity.getSyncTransactionCommitCallbackDepth() > 0
+ || activity.mSyncState != SYNC_STATE_NONE) {
return;
}
if (allWindowDrawn) {
@@ -1323,16 +1393,13 @@ class BackNavigationController {
if (!allWindowDrawn) {
return;
}
- final SurfaceControl startingSurface = mOpenAnimAdaptor.mStartingSurface;
- if (startingSurface != null && startingSurface.isValid()) {
- startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
- synchronized (mWindowManagerService.mGlobalLock) {
- if (mOpenAnimAdaptor != null) {
- mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
- }
+ startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
+ synchronized (mWindowManagerService.mGlobalLock) {
+ if (mOpenAnimAdaptor != null) {
+ mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
}
- });
- }
+ }
+ });
}
void clearBackAnimateTarget(boolean cancel) {
@@ -1512,6 +1579,9 @@ class BackNavigationController {
}
void createStartingSurface(@Nullable TaskSnapshot snapshot) {
+ if (Flags.deferPredictiveAnimationIfNoSnapshot() && snapshot == null) {
+ return;
+ }
if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
return;
}
@@ -1722,8 +1792,7 @@ class BackNavigationController {
ActivityRecord currentActivity,
ArrayList<ActivityRecord> previousActivity,
WindowContainer removedWindowContainer) {
- final ScheduleAnimationBuilder builder =
- new ScheduleAnimationBuilder(backType, adapter, monitor);
+ final ScheduleAnimationBuilder builder = new ScheduleAnimationBuilder(adapter, monitor);
switch (backType) {
case BackNavigationInfo.TYPE_RETURN_TO_HOME:
return builder
@@ -1748,7 +1817,6 @@ class BackNavigationController {
}
class ScheduleAnimationBuilder {
- final int mType;
final BackAnimationAdapter mBackAnimationAdapter;
final NavigationMonitor mNavigationMonitor;
WindowContainer mCloseTarget;
@@ -1756,9 +1824,8 @@ class BackNavigationController {
boolean mIsLaunchBehind;
TaskSnapshot mSnapshot;
- ScheduleAnimationBuilder(int type, BackAnimationAdapter adapter,
+ ScheduleAnimationBuilder(BackAnimationAdapter adapter,
NavigationMonitor monitor) {
- mType = type;
mBackAnimationAdapter = adapter;
mNavigationMonitor = monitor;
}
@@ -1789,7 +1856,36 @@ class BackNavigationController {
}
private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities) {
- if (mSnapshot == null) {
+ if (Flags.unifyBackNavigationTransition()) {
+ if (mCloseTarget.asWindowState() != null) {
+ return null;
+ }
+ final ArrayList<ActivityRecord> makeVisibles = new ArrayList<>();
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ final ActivityRecord activity = visibleOpenActivities[i];
+ if (activity.mLaunchTaskBehind || activity.isVisibleRequested()) {
+ continue;
+ }
+ makeVisibles.add(activity);
+ }
+ final TransitionController tc = visibleOpenActivities[0].mTransitionController;
+ final Transition prepareOpen = tc.createTransition(
+ TRANSIT_PREPARE_BACK_NAVIGATION);
+ tc.collect(mCloseTarget);
+ prepareOpen.setBackGestureAnimation(mCloseTarget, true /* isTop */);
+ for (int i = mOpenTargets.length - 1; i >= 0; --i) {
+ tc.collect(mOpenTargets[i]);
+ prepareOpen.setBackGestureAnimation(mOpenTargets[i], false /* isTop */);
+ }
+ if (!makeVisibles.isEmpty()) {
+ setLaunchBehind(visibleOpenActivities);
+ }
+ tc.requestStartTransition(prepareOpen,
+ null /*startTask */, null /* remoteTransition */,
+ null /* displayChange */);
+ prepareOpen.setReady(mCloseTarget, true);
+ return prepareOpen;
+ } else if (mSnapshot == null) {
return setLaunchBehind(visibleOpenActivities);
}
return null;
@@ -1986,6 +2082,7 @@ class BackNavigationController {
private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) {
final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
+ final boolean unifyBackNavigationTransition = Flags.unifyBackNavigationTransition();
final ArrayList<ActivityRecord> affects = new ArrayList<>();
for (int i = activities.length - 1; i >= 0; --i) {
final ActivityRecord activity = activities[i];
@@ -1999,8 +2096,8 @@ class BackNavigationController {
}
final TransitionController tc = activities[0].mTransitionController;
- final Transition prepareOpen = migrateBackTransition && !tc.isCollecting()
- ? tc.createTransition(TRANSIT_PREPARE_BACK_NAVIGATION) : null;
+ final Transition prepareOpen = migrateBackTransition && !unifyBackNavigationTransition
+ && !tc.isCollecting() ? tc.createTransition(TRANSIT_PREPARE_BACK_NAVIGATION) : null;
DisplayContent commonDisplay = null;
for (int i = affects.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 2259b5a5b08c..852a0ac054f4 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,12 +46,10 @@ import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balAdditionalStartModes;
import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
-import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balImprovedMetrics;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
-import static com.android.window.flags.Flags.balRequireOptInSameUid;
-import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
+import static com.android.window.flags.Flags.balStrictModeRo;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -63,6 +61,7 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
+import android.app.IBackgroundActivityLaunchCallback;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -70,13 +69,17 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.widget.Toast;
import com.android.internal.R;
@@ -91,6 +94,7 @@ import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfigura
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -141,6 +145,10 @@ public class BackgroundActivityStartController {
private final ActivityTaskManagerService mService;
private final ActivityTaskSupervisor mSupervisor;
+ @GuardedBy("mStrictModeBalCallbacks")
+ private final SparseArray<ArrayMap<IBinder, IBackgroundActivityLaunchCallback>>
+ mStrictModeBalCallbacks = new SparseArray<>();
+
// TODO(b/263368846) Rename when ASM logic is moved in
@Retention(SOURCE)
@@ -289,7 +297,8 @@ public class BackgroundActivityStartController {
private final int mCallingUid;
private final int mCallingPid;
private final @ActivityTaskManagerService.AppSwitchState int mAppSwitchState;
- private final boolean mCallingUidHasAnyVisibleWindow;
+ private final boolean mCallingUidHasVisibleActivity;
+ private final boolean mCallingUidHasNonAppVisibleWindow;
private final @ActivityManager.ProcessState int mCallingUidProcState;
private final boolean mIsCallingUidPersistentSystemProcess;
final BackgroundStartPrivileges mBalAllowedByPiSender;
@@ -298,11 +307,12 @@ public class BackgroundActivityStartController {
private final String mRealCallingPackage;
private final int mRealCallingUid;
private final int mRealCallingPid;
- private final boolean mRealCallingUidHasAnyVisibleWindow;
+ private final boolean mRealCallingUidHasVisibleActivity;
+ private final boolean mRealCallingUidHasNonAppVisibleWindow;
private final @ActivityManager.ProcessState int mRealCallingUidProcState;
private final boolean mIsRealCallingUidPersistentSystemProcess;
private final PendingIntentRecord mOriginatingPendingIntent;
- private final BackgroundStartPrivileges mForcedBalByPiSender;
+ private final boolean mAllowBalExemptionForSystemProcess;
private final Intent mIntent;
private final WindowProcessController mCallerApp;
private final WindowProcessController mRealCallerApp;
@@ -317,7 +327,7 @@ public class BackgroundActivityStartController {
int realCallingUid, int realCallingPid,
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender,
+ boolean allowBalExemptionForSystemProcess,
ActivityRecord resultRecord,
Intent intent,
ActivityOptions checkedOptions) {
@@ -327,7 +337,7 @@ public class BackgroundActivityStartController {
mRealCallingUid = realCallingUid;
mRealCallingPid = realCallingPid;
mCallerApp = callerApp;
- mForcedBalByPiSender = forcedBalByPiSender;
+ mAllowBalExemptionForSystemProcess = allowBalExemptionForSystemProcess;
mOriginatingPendingIntent = originatingPendingIntent;
mIntent = intent;
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
@@ -338,17 +348,13 @@ public class BackgroundActivityStartController {
@BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
checkedOptions.getPendingIntentBackgroundActivityStartMode();
- if (!balImproveRealCallerVisibilityCheck()) {
- // without this fix the auto-opt ins below would violate CTS tests
- mAutoOptInReason = null;
- mAutoOptInCaller = false;
- } else if (originatingPendingIntent == null) {
+ if (originatingPendingIntent == null) {
mAutoOptInReason = AUTO_OPT_IN_NOT_PENDING_INTENT;
mAutoOptInCaller = true;
} else if (mIsCallForResult) {
mAutoOptInReason = AUTO_OPT_IN_CALL_FOR_RESULT;
mAutoOptInCaller = false;
- } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+ } else if (callingUid == realCallingUid) {
mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
mAutoOptInCaller = false;
} else if (realCallerBackgroundActivityStartMode
@@ -397,16 +403,21 @@ public class BackgroundActivityStartController {
mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
mIsCallingUidPersistentSystemProcess =
mCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
- mCallingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
+ mCallingUidHasVisibleActivity =
+ mService.mVisibleActivityProcessTracker.hasVisibleActivity(callingUid);
+ mCallingUidHasNonAppVisibleWindow = mService.mActiveUids.hasNonAppVisibleWindow(
+ callingUid);
if (realCallingUid == NO_PROCESS_UID) {
// no process provided
mRealCallingUidProcState = PROCESS_STATE_NONEXISTENT;
- mRealCallingUidHasAnyVisibleWindow = false;
+ mRealCallingUidHasVisibleActivity = false;
+ mRealCallingUidHasNonAppVisibleWindow = false;
mRealCallerApp = null;
mIsRealCallingUidPersistentSystemProcess = false;
} else if (callingUid == realCallingUid) {
mRealCallingUidProcState = mCallingUidProcState;
- mRealCallingUidHasAnyVisibleWindow = mCallingUidHasAnyVisibleWindow;
+ mRealCallingUidHasVisibleActivity = mCallingUidHasVisibleActivity;
+ mRealCallingUidHasNonAppVisibleWindow = mCallingUidHasNonAppVisibleWindow;
// In the PendingIntent case callerApp is not passed in, so resolve it ourselves.
mRealCallerApp = callerApp == null
? mService.getProcessController(realCallingPid, realCallingUid)
@@ -414,8 +425,10 @@ public class BackgroundActivityStartController {
mIsRealCallingUidPersistentSystemProcess = mIsCallingUidPersistentSystemProcess;
} else {
mRealCallingUidProcState = mService.mActiveUids.getUidState(realCallingUid);
- mRealCallingUidHasAnyVisibleWindow =
- mService.hasActiveVisibleWindow(realCallingUid);
+ mRealCallingUidHasVisibleActivity =
+ mService.mVisibleActivityProcessTracker.hasVisibleActivity(realCallingUid);
+ mRealCallingUidHasNonAppVisibleWindow =
+ mService.mActiveUids.hasNonAppVisibleWindow(realCallingUid);
mRealCallerApp = mService.getProcessController(realCallingPid, realCallingUid);
mIsRealCallingUidPersistentSystemProcess =
mRealCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
@@ -542,12 +555,15 @@ public class BackgroundActivityStartController {
sb.append("; callingUid: ").append(mCallingUid);
sb.append("; callingPid: ").append(mCallingPid);
sb.append("; appSwitchState: ").append(mAppSwitchState);
- sb.append("; callingUidHasAnyVisibleWindow: ").append(mCallingUidHasAnyVisibleWindow);
+ sb.append("; callingUidHasVisibleActivity: ").append(mCallingUidHasVisibleActivity);
+ sb.append("; callingUidHasNonAppVisibleWindow: ").append(
+ mCallingUidHasNonAppVisibleWindow);
sb.append("; callingUidProcState: ").append(DebugUtils.valueToString(
ActivityManager.class, "PROCESS_STATE_", mCallingUidProcState));
sb.append("; isCallingUidPersistentSystemProcess: ")
.append(mIsCallingUidPersistentSystemProcess);
- sb.append("; forcedBalByPiSender: ").append(mForcedBalByPiSender);
+ sb.append("; allowBalExemptionForSystemProcess: ")
+ .append(mAllowBalExemptionForSystemProcess);
sb.append("; intent: ").append(mIntent);
sb.append("; callerApp: ").append(mCallerApp);
if (mCallerApp != null) {
@@ -558,7 +574,7 @@ public class BackgroundActivityStartController {
.append(mBalAllowedByPiCreatorWithHardening);
sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller);
sb.append("; callerStartMode: ").append(balStartModeToString(
- mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
+ mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()));
sb.append("; hasRealCaller: ").append(hasRealCaller());
sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
@@ -570,8 +586,10 @@ public class BackgroundActivityStartController {
.append(getTargetSdk(mRealCallingPackage));
sb.append("; realCallingUid: ").append(mRealCallingUid);
sb.append("; realCallingPid: ").append(mRealCallingPid);
- sb.append("; realCallingUidHasAnyVisibleWindow: ")
- .append(mRealCallingUidHasAnyVisibleWindow);
+ sb.append("; realCallingUidHasVisibleActivity: ")
+ .append(mRealCallingUidHasVisibleActivity);
+ sb.append("; realCallingUidHasNonAppVisibleWindow: ")
+ .append(mRealCallingUidHasNonAppVisibleWindow);
sb.append("; realCallingUidProcState: ").append(DebugUtils.valueToString(
ActivityManager.class, "PROCESS_STATE_", mRealCallingUidProcState));
sb.append("; isRealCallingUidPersistentSystemProcess: ")
@@ -585,16 +603,11 @@ public class BackgroundActivityStartController {
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
sb.append("; realCallerStartMode: ").append(balStartModeToString(
- mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()));
+ mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
}
// features
- sb.append("; balImproveRealCallerVisibilityCheck: ")
- .append(balImproveRealCallerVisibilityCheck());
sb.append("; balRequireOptInByPendingIntentCreator: ")
.append(balRequireOptInByPendingIntentCreator());
- sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
- sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
- .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
.append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
@@ -702,8 +715,8 @@ public class BackgroundActivityStartController {
* @param callerApp The process that calls this method (only if not a PendingIntent)
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
* null if not originated by PendingIntent
- * @param forcedBalByPiSender If set to allow, the
- * PendingIntent's sender will try to force allow background activity starts.
+ * @param allowBalExemptionForSystemProcess If set to true, the
+ * PendingIntent's sender will allow additional exemptions.
* This is only possible if the sender of the PendingIntent is a system process.
* @param resultRecord If not null, this indicates that the caller expects a result.
* @param intent Intent that should be started.
@@ -720,7 +733,7 @@ public class BackgroundActivityStartController {
int realCallingPid,
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
- BackgroundStartPrivileges forcedBalByPiSender,
+ boolean allowBalExemptionForSystemProcess,
ActivityRecord resultRecord,
Intent intent,
ActivityOptions checkedOptions) {
@@ -732,7 +745,7 @@ public class BackgroundActivityStartController {
BalState state = new BalState(callingUid, callingPid, callingPackage,
realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
- forcedBalByPiSender, resultRecord, intent, checkedOptions);
+ allowBalExemptionForSystemProcess, resultRecord, intent, checkedOptions);
// In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
// visible window.
@@ -792,7 +805,8 @@ public class BackgroundActivityStartController {
// Allowed before V by creator
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
- + " if the PI creator upgrades target_sdk to 35+! "
+ + " if the PI creator upgrades target_sdk to 35+!"
+ + " goo.gle/android-bal"
+ " (missing opt in by PI creator)!" + state);
return allowBasedOnCaller(state);
}
@@ -802,6 +816,7 @@ public class BackgroundActivityStartController {
if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ + " goo.gle/android-bal"
+ " (missing opt in by PI sender)!" + state);
return allowBasedOnRealCaller(state);
}
@@ -829,13 +844,127 @@ public class BackgroundActivityStartController {
}
private BalVerdict abortLaunch(BalState state) {
- Slog.wtf(TAG, "Background activity launch blocked! " + state);
+ Slog.wtf(TAG, "Background activity launch blocked! goo.gle/android-bal "
+ + state);
if (balShowToastsBlocked()
&& (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
// only show a toast if either caller or real caller could launch if they opted in
showToast("BAL blocked. goo.gle/android-bal");
}
- return statsLog(BalVerdict.BLOCK, state);
+ BalVerdict verdict = statsLog(BalVerdict.BLOCK, state);
+ if (balStrictModeRo()) {
+ String abortDebugMessage;
+ if (state.isPendingIntent()) {
+ abortDebugMessage =
+ "PendingIntent Activity start blocked in " + state.mRealCallingPackage
+ + ". "
+ + "PendingIntent was created in " + state.mCallingPackage
+ + ". "
+ + (state.mResultForRealCaller.allows()
+ ? state.mRealCallingPackage
+ + " could opt in to grant BAL privileges when sending. "
+ : "")
+ + (state.mResultForCaller.allows()
+ ? state.mCallingPackage
+ + " could opt in to grant BAL privileges when creating."
+ : "")
+ + "The intent would have started " + state.mIntent.getComponent();
+ } else {
+ abortDebugMessage = "Activity start blocked. "
+ + "The intent would have started " + state.mIntent.getComponent();
+ }
+ strictModeLaunchAborted(state.mCallingUid, abortDebugMessage);
+ if (!state.callerIsRealCaller()) {
+ strictModeLaunchAborted(state.mRealCallingUid, abortDebugMessage);
+ }
+ }
+ return verdict;
+ }
+
+ /**
+ * Retrieve a registered strict mode callback for BAL.
+ * @param uid the uid of the app.
+ * @return the callback if it exists, returns <code>null</code> otherwise.
+ */
+ @Nullable
+ Map<IBinder, IBackgroundActivityLaunchCallback> getStrictModeBalCallbacks(int uid) {
+ ArrayMap<IBinder, IBackgroundActivityLaunchCallback> callbackMap;
+ synchronized (mStrictModeBalCallbacks) {
+ callbackMap =
+ mStrictModeBalCallbacks.get(uid);
+ if (callbackMap == null) {
+ return null;
+ }
+ return new ArrayMap<>(callbackMap);
+ }
+ }
+
+ /**
+ * Add strict mode callback for BAL.
+ *
+ * @param uid the UID for which the binder is registered.
+ * @param callback the {@link IBackgroundActivityLaunchCallback} binder to call when BAL is
+ * blocked.
+ * @return {@code true} if the callback has been successfully added.
+ */
+ boolean addStrictModeCallback(int uid, IBinder callback) {
+ IBackgroundActivityLaunchCallback balCallback =
+ IBackgroundActivityLaunchCallback.Stub.asInterface(callback);
+ synchronized (mStrictModeBalCallbacks) {
+ ArrayMap<IBinder, IBackgroundActivityLaunchCallback> callbackMap =
+ mStrictModeBalCallbacks.get(uid);
+ if (callbackMap == null) {
+ callbackMap = new ArrayMap<>();
+ mStrictModeBalCallbacks.put(uid, callbackMap);
+ }
+ if (callbackMap.containsKey(callback)) {
+ return false;
+ }
+ callbackMap.put(callback, balCallback);
+ }
+ try {
+ callback.linkToDeath(() -> removeStrictModeCallback(uid, callback), 0);
+ } catch (RemoteException e) {
+ removeStrictModeCallback(uid, callback);
+ }
+ return true;
+ }
+
+ /**
+ * Remove strict mode callback for BAL.
+ *
+ * @param uid the UID for which the binder is registered.
+ * @param callback the {@link IBackgroundActivityLaunchCallback} binder to call when BAL is
+ * blocked.
+ */
+ void removeStrictModeCallback(int uid, IBinder callback) {
+ synchronized (mStrictModeBalCallbacks) {
+ Map<IBinder, IBackgroundActivityLaunchCallback> callbackMap =
+ mStrictModeBalCallbacks.get(uid);
+ if (callback == null || !callbackMap.containsKey(callback)) {
+ return;
+ }
+ callbackMap.remove(callback);
+ if (callbackMap.isEmpty()) {
+ mStrictModeBalCallbacks.remove(uid);
+ }
+ }
+ }
+
+ private void strictModeLaunchAborted(int callingUid, String message) {
+ Map<IBinder, IBackgroundActivityLaunchCallback> strictModeBalCallbacks =
+ getStrictModeBalCallbacks(callingUid);
+ if (strictModeBalCallbacks == null) {
+ return;
+ }
+ for (Map.Entry<IBinder, IBackgroundActivityLaunchCallback> callbackEntry :
+ strictModeBalCallbacks.entrySet()) {
+ try {
+ callbackEntry.getValue().onBackgroundActivityLaunchAborted(message);
+ } catch (RemoteException e) {
+ removeStrictModeCallback(callingUid, callbackEntry.getKey());
+ }
+ }
}
/**
@@ -881,13 +1010,14 @@ public class BackgroundActivityStartController {
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY;
- if (appSwitchAllowedOrFg && state.mCallingUidHasAnyVisibleWindow) {
+ if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "callingUid has visible window");
}
- if (mService.mActiveUids.hasNonAppVisibleWindow(state.mCallingUid)) {
+ if (state.mCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "callingUid has non-app visible window");
+ /*background*/ false, "callingUid has non-app visible window "
+ + mService.mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid));
}
// Don't abort if the callerApp or other processes of that uid are considered to be in the
// foreground.
@@ -1007,23 +1137,14 @@ public class BackgroundActivityStartController {
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
- if (balImproveRealCallerVisibilityCheck()) {
- if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has visible window");
- }
- if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
- return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has non-app visible window");
- }
- } else {
- // don't abort if the realCallingUid has a visible window
- // TODO(b/171459802): We should check appSwitchAllowed also
- if (state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false,
- "realCallingUid has visible (non-toast) window.");
- }
+ if (appSwitchAllowedOrFg && state.mRealCallingUidHasVisibleActivity) {
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+ /*background*/ false, "realCallingUid has visible window");
+ }
+ if (state.mRealCallingUidHasNonAppVisibleWindow) {
+ return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
+ /*background*/ false, "realCallingUid has non-app visible window "
+ + mService.mActiveUids.getNonAppVisibleWindowDetails(state.mRealCallingUid));
}
// Don't abort if the realCallerApp or other processes of that uid are considered to be in
@@ -1036,17 +1157,35 @@ public class BackgroundActivityStartController {
* or {@link #BAL_BLOCK} if the launch should be blocked
*/
BalVerdict checkBackgroundActivityStartAllowedByRealCallerInBackground(BalState state) {
- if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+ boolean allowAlways = state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+ if (allowAlways
&& hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
/*background*/ false,
"realCallingUid has BAL permission.");
}
+ // don't abort if the realCallingUid has SYSTEM_ALERT_WINDOW permission
+ Slog.i(TAG, "hasSystemAlertWindowPermission(" + state.mRealCallingUid + ", "
+ + state.mRealCallingPid + ", " + state.mRealCallingPackage + ") "
+ + balStartModeToString(
+ state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
+ if (allowAlways
+ && mService.hasSystemAlertWindowPermission(state.mRealCallingUid,
+ state.mRealCallingPid, state.mRealCallingPackage)) {
+ Slog.w(
+ TAG,
+ "Background activity start for "
+ + state.mRealCallingPackage
+ + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
+ return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
+ /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+ }
+
// if the realCallingUid is a persistent system process, abort if the IntentSender
// wasn't allowed to start an activity
- if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
+ if ((allowAlways || state.mAllowBalExemptionForSystemProcess)
&& state.mIsRealCallingUidPersistentSystemProcess) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
/*background*/ false,
@@ -1120,7 +1259,9 @@ public class BackgroundActivityStartController {
@Nullable Task targetTask, int launchFlags, int balCode, int callingUid,
int realCallingUid, TaskDisplayArea preferredTaskDisplayArea) {
// BAL Exception allowed in all cases
- if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ if (balCode == BAL_ALLOW_ALLOWLISTED_UID
+ || (android.security.Flags.asmReintroduceGracePeriod()
+ && balCode == BAL_ALLOW_GRACE_PERIOD)) {
return true;
}
@@ -1173,10 +1314,15 @@ public class BackgroundActivityStartController {
ArrayList<Task> visibleTasks = displayArea.getVisibleTasks();
for (int i = 0; i < visibleTasks.size(); i++) {
Task task = visibleTasks.get(i);
- if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) {
- bas.optedIn(task.getTopMostActivity());
- } else {
+ if (android.security.Flags.asmReintroduceGracePeriod()) {
bas = checkTopActivityForAsm(task, callingUid, /*sourceRecord*/null, bas);
+ } else {
+ if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) {
+ bas.optedIn(task.getTopMostActivity());
+ } else {
+ bas = checkTopActivityForAsm(
+ task, callingUid, /*sourceRecord*/null, bas);
+ }
}
}
}
@@ -1355,7 +1501,7 @@ public class BackgroundActivityStartController {
String packageName = mService.mContext.getPackageManager().getNameForUid(callingUid);
BalState state = new BalState(callingUid, callingPid, packageName, INVALID_UID,
- INVALID_PID, null, null, null, null, null, ActivityOptions.makeBasic());
+ INVALID_PID, null, null, false, null, null, ActivityOptions.makeBasic());
@BalCode int balCode = checkBackgroundActivityStartAllowedByCaller(state).mCode;
if (balCode == BAL_ALLOW_ALLOWLISTED_UID
|| balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
@@ -1741,27 +1887,17 @@ public class BackgroundActivityStartController {
state.mCallingUid,
state.mCallingPackage,
state.mCallingUidProcState,
- state.mCallingUidHasAnyVisibleWindow,
+ state.mCallingUidHasVisibleActivity
+ || state.mCallingUidHasNonAppVisibleWindow,
state.mRealCallingUid,
state.mRealCallingUidProcState,
- state.mRealCallingUidHasAnyVisibleWindow,
+ state.mRealCallingUidHasVisibleActivity
+ || state.mRealCallingUidHasNonAppVisibleWindow,
(state.mOriginatingPendingIntent != null));
}
- if (finalVerdict.getRawCode() == BAL_ALLOW_GRACE_PERIOD) {
- if (state.realCallerExplicitOptInOrAutoOptIn()
- && state.mResultForRealCaller.allows()
- && state.mResultForRealCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) {
- // real caller could allow with a different exemption
- } else if (state.callerExplicitOptInOrAutoOptIn() && state.mResultForCaller.allows()
- && state.mResultForCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) {
- // caller could allow with a different exemption
- } else {
- // log to determine grace period length distribution
- Slog.wtf(TAG, "Activity start ONLY allowed by BAL_ALLOW_GRACE_PERIOD "
- + finalVerdict.mMessage + ": " + state);
- }
- }
+ logIfOnlyAllowedBy(finalVerdict, state, BAL_ALLOW_GRACE_PERIOD);
+ logIfOnlyAllowedBy(finalVerdict, state, BAL_ALLOW_NON_APP_VISIBLE_WINDOW);
if (balImprovedMetrics()) {
if (shouldLogStats(finalVerdict, state)) {
@@ -1800,6 +1936,30 @@ public class BackgroundActivityStartController {
return finalVerdict;
}
+ /**
+ * Logs details about the activity starts if the only reason it is allowed is the provided
+ * {@code balCode}.
+ */
+ private static void logIfOnlyAllowedBy(BalVerdict finalVerdict, BalState state, int balCode) {
+ if (finalVerdict.getRawCode() == balCode) {
+ if (state.realCallerExplicitOptInOrAutoOptIn()
+ && state.mResultForRealCaller != null
+ && state.mResultForRealCaller.allows()
+ && state.mResultForRealCaller.getRawCode() != balCode) {
+ // real caller could allow with a different exemption
+ } else if (state.callerExplicitOptInOrAutoOptIn()
+ && state.mResultForCaller != null
+ && state.mResultForCaller.allows()
+ && state.mResultForCaller.getRawCode() != balCode) {
+ // caller could allow with a different exemption
+ } else {
+ // log to determine grace period length distribution
+ Slog.wtf(TAG, "Activity start ONLY allowed by " + balCodeToString(balCode) + " "
+ + finalVerdict.mMessage + ": " + state);
+ }
+ }
+ }
+
@VisibleForTesting
boolean shouldLogStats(BalVerdict finalVerdict, BalState state) {
if (finalVerdict.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) {
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 1073713cca52..ccf1aedb3177 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -50,7 +50,6 @@ import android.util.IntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -137,16 +136,14 @@ class BackgroundLaunchProcessController {
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
- if (checkConfiguration.checkVisibility && (
- Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
- ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
- : isBoundByForegroundUid())) {
+ if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
+ && isBoundByForegroundUid()) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
: BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
- if (checkConfiguration.checkVisibility && hasActivityInVisibleTask
+ if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
&& appSwitchState != APP_SWITCH_DISALLOW) {
return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
"process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index ba4ab7df8270..7d6cf0024b75 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
import android.graphics.Rect;
import android.view.Surface.OutOfResourcesException;
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index e3232e08749e..ae65db46b242 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -21,6 +21,8 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -29,20 +31,22 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.ActivityInfo;
+import android.app.CameraCompatTaskInfo;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.os.RemoteException;
import android.view.DisplayInfo;
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
-import com.android.window.flags.Flags;
+import com.android.internal.protolog.WmProtoLogGroups;
/**
* Policy for camera compatibility freeform treatment.
@@ -63,8 +67,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
@NonNull
private final CameraStateMonitor mCameraStateMonitor;
- private boolean mIsCameraCompatTreatmentPending = false;
-
+ // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
+ // all current camera activities, especially when the camera access is switching from one app to
+ // another.
@Nullable
private Task mCameraTask;
@@ -99,87 +104,143 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
return mIsRunning;
}
- // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
- // treatment is enabled.
+ // Refreshing only when configuration changes after applying camera compat treatment.
@Override
public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig,
@NonNull Configuration lastReportedConfig) {
- return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending;
+ return isTreatmentEnabledForActivity(activity, /* shouldCheckOrientation= */ true)
+ && haveCameraCompatAttributesChanged(newConfig, lastReportedConfig);
}
- /**
- * Whether activity is eligible for camera compatibility free-form treatment.
- *
- * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
- * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
- * provides changes to the camera and display orientation signals to match those expected on a
- * portrait device in that orientation (for example, on a standard phone).
- *
- * <p>The treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Property gating the camera compatibility free-form treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override.
- * </ul>
- */
- @VisibleForTesting
- boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) {
- return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled(
- ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
+ private boolean haveCameraCompatAttributesChanged(@NonNull Configuration newConfig,
+ @NonNull Configuration lastReportedConfig) {
+ // Camera compat treatment changes the following:
+ // - Letterboxes app bounds to camera compat aspect ratio in app's requested orientation,
+ // - Changes display rotation so it matches what the app expects in its chosen orientation,
+ // - Rotate-and-crop camera feed to match that orientation (this changes iff the display
+ // rotation changes, so no need to check).
+ final long diff = newConfig.windowConfiguration.diff(lastReportedConfig.windowConfiguration,
+ /* compareUndefined= */ true);
+ final boolean appBoundsChanged = (diff & WINDOW_CONFIG_APP_BOUNDS) != 0;
+ final boolean displayRotationChanged = (diff & WINDOW_CONFIG_DISPLAY_ROTATION) != 0;
+ return appBoundsChanged || displayRotationChanged;
}
@Override
- public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
- if (!isTreatmentEnabledForActivity(cameraActivity)) {
+ public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
+ // Do not check orientation outside of the config recompute, as the app's orientation intent
+ // might be obscured by a fullscreen override. Especially for apps which have a camera
+ // functionality which is not the main focus of the app: while most of the app might work
+ // well in fullscreen, often the camera setup still assumes it will run on a portrait device
+ // in its natural orientation and comes out stretched or sideways.
+ // Config recalculation will later check the original orientation to avoid applying
+ // treatment to apps optimized for large screens.
+ if (!isTreatmentEnabledForActivity(cameraActivity, /* shouldCheckOrientation= */ false)) {
return;
}
- final int existingCameraCompatMode = cameraActivity.mAppCompatController
- .getAppCompatCameraOverrides()
- .getFreeformCameraCompatMode();
- final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
- if (newCameraCompatMode != existingCameraCompatMode) {
- mIsCameraCompatTreatmentPending = true;
- mCameraTask = cameraActivity.getTask();
- cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
- .setFreeformCameraCompatMode(newCameraCompatMode);
- forceUpdateActivityAndTask(cameraActivity);
- } else {
- mIsCameraCompatTreatmentPending = false;
- }
+
+ mCameraTask = cameraActivity.getTask();
+ updateAndDispatchCameraConfiguration();
}
@Override
- public boolean onCameraClosed(@NonNull String cameraId) {
+ public boolean canCameraBeClosed(@NonNull String cameraId) {
// Top activity in the same task as the camera activity, or `null` if the task is
// closed.
- final ActivityRecord topActivity = mCameraTask != null
- ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
- : null;
+ final ActivityRecord topActivity = getTopActivityFromCameraTask();
if (topActivity != null) {
if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_STATES,
"Display id=%d is notified that Camera %s is closed but activity is"
+ " still refreshing. Rescheduling an update.",
mDisplayContent.mDisplayId, cameraId);
return false;
}
}
- mCameraTask = null;
- mIsCameraCompatTreatmentPending = false;
return true;
}
- private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) {
- cameraActivity.recomputeConfiguration();
- cameraActivity.updateReportedConfigurationAndSend();
- Task cameraTask = cameraActivity.getTask();
- if (cameraTask != null) {
- cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+ @Override
+ public void onCameraClosed() {
+ // Top activity in the same task as the camera activity, or `null` if the task is
+ // closed.
+ final ActivityRecord topActivity = getTopActivityFromCameraTask();
+ // Only clean up if the camera is not running - this close signal could be from switching
+ // cameras (e.g. back to front camera, and vice versa).
+ if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
+ updateAndDispatchCameraConfiguration();
+ mCameraTask = null;
+ }
+ }
+
+ private void updateAndDispatchCameraConfiguration() {
+ if (mCameraTask == null) {
+ return;
+ }
+ final ActivityRecord activity = getTopActivityFromCameraTask();
+ if (activity != null) {
+ activity.recomputeConfiguration();
+ mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+ updateCompatibilityInfo(activity);
+ activity.ensureActivityConfiguration(/* ignoreVisibility= */ true);
+ } else {
+ mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+ }
+ }
+
+ private void updateCompatibilityInfo(@NonNull ActivityRecord activityRecord) {
+ final CompatibilityInfo compatibilityInfo = activityRecord.mAtmService
+ .compatibilityInfoForPackageLocked(activityRecord.info.applicationInfo);
+ compatibilityInfo.applicationDisplayRotation =
+ CameraCompatTaskInfo.getDisplayRotationFromCameraCompatMode(
+ getCameraCompatMode(activityRecord));
+ try {
+ // TODO(b/380840084): Consider using a ClientTransaction for this update.
+ activityRecord.app.getThread().updatePackageCompatibilityInfo(
+ activityRecord.packageName, compatibilityInfo);
+ } catch (RemoteException e) {
+ ProtoLog.w(WmProtoLogGroups.WM_DEBUG_STATES,
+ "Unable to update CompatibilityInfo for app %s", activityRecord.app);
+ }
+ }
+
+ boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ return isCameraRunningAndWindowingModeEligible(activity);
+ }
+
+ boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
+ return activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldApplyFreeformTreatmentForCameraCompat()
+ && activity.inFreeformWindowingMode()
+ && mCameraStateMonitor.isCameraRunningForActivity(activity);
+ }
+
+ boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+ // Camera compat should direct aspect ratio when in camera compat mode, unless an app has a
+ // different camera compat aspect ratio set: this allows per-app camera compat override
+ // aspect ratio to be smaller than the default.
+ return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController
+ .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
+ }
+
+ boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) {
+ return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE;
+ }
+
+ float getCameraCompatAspectRatio(@NonNull ActivityRecord activityRecord) {
+ if (shouldCameraCompatControlAspectRatio(activityRecord)) {
+ return activityRecord.mWmService.mAppCompatConfiguration.getCameraCompatAspectRatio();
}
+
+ return MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
}
- private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) {
+ @CameraCompatTaskInfo.FreeformCameraCompatMode
+ int getCameraCompatMode(@NonNull ActivityRecord topActivity) {
+ if (!isTreatmentEnabledForActivity(topActivity, /* shouldCheckOrientation= */ true)) {
+ return CAMERA_COMPAT_FREEFORM_NONE;
+ }
final int appOrientation = topActivity.getRequestedConfigurationOrientation();
// It is very important to check the original (actual) display rotation, and not the
// sandboxed rotation that camera compat treatment sets.
@@ -219,15 +280,25 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
* <ul>
* <li>Treatment is enabled.
* <li>Camera is active for the package.
- * <li>The app has a fixed orientation.
+ * <li>The app has a fixed orientation if {@code checkOrientation} is true.
* <li>The app is in freeform windowing mode.
* </ul>
+ *
+ * @param checkOrientation Whether to take apps orientation into account for this check. Only
+ * fixed-orientation apps should be targeted, but this might be
+ * obscured by OEMs via fullscreen override and the app's original
+ * intent inaccessible when the camera opens. Thus, policy would pass
+ * {@code false} here when considering whether to trigger config
+ * recalculation, and later pass {@code true} during recalculation.
*/
- private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
+ @VisibleForTesting
+ boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity,
+ boolean checkOrientation) {
int orientation = activity.getRequestedConfigurationOrientation();
- return shouldApplyFreeformTreatmentForCameraCompat(activity)
+ return activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldApplyFreeformTreatmentForCameraCompat()
&& mCameraStateMonitor.isCameraRunningForActivity(activity)
- && orientation != ORIENTATION_UNDEFINED
+ && (!checkOrientation || orientation != ORIENTATION_UNDEFINED)
&& activity.inFreeformWindowingMode()
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force-letterbox them.
@@ -237,10 +308,17 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
&& !activity.isEmbedded();
}
+ @Nullable
+ private ActivityRecord getTopActivityFromCameraTask() {
+ return mCameraTask != null
+ ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
+ : null;
+ }
+
private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
@NonNull String cameraId) {
- if (!isTreatmentEnabledForActivity(topActivity)
- || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
+ if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true)
+ || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 4140c046f968..00279921953d 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -15,7 +15,7 @@
*/
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -67,6 +67,10 @@ class CameraStateMonitor {
// when camera connection is closed and we need to clean up our records.
private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping =
new CameraIdPackageNameBiMapping();
+ // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to
+ // keep track of camera-closed signals when apps are switching camera access, so that the policy
+ // can restore app configuration when an app closes camera (e.g. loses camera access due to
+ // another app).
private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
// TODO(b/336474959): should/can this go in the compat listeners?
@@ -165,15 +169,14 @@ class CameraStateMonitor {
if (cameraActivity == null || cameraActivity.getTask() == null) {
return;
}
- notifyListenersCameraOpened(cameraActivity, cameraId);
+ notifyListenersCameraOpened(cameraActivity);
}
}
- private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity) {
for (int i = 0; i < mCameraStateListeners.size(); i++) {
CameraCompatStateListener listener = mCameraStateListeners.get(i);
- listener.onCameraOpened(cameraActivity, cameraId);
+ listener.onCameraOpened(cameraActivity);
}
}
@@ -226,11 +229,11 @@ class CameraStateMonitor {
// Already reconnected to this camera, no need to clean up.
return;
}
-
- final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId);
- if (closeSuccessfulForAllListeners) {
+ final boolean canClose = checkCanCloseForAllListeners(cameraId);
+ if (canClose) {
// Finish cleaning up.
mCameraIdPackageBiMapping.removeCameraId(cameraId);
+ notifyListenersCameraClosed();
} else {
// Not ready to process closure yet - the camera activity might be refreshing.
// Try again later.
@@ -240,15 +243,21 @@ class CameraStateMonitor {
}
/**
- * @return {@code false} if any listeners have reported issues processing the close.
+ * @return {@code false} if any listener has reported that they cannot process camera close now.
*/
- private boolean notifyListenersCameraClosed(@NonNull String cameraId) {
- boolean closeSuccessfulForAllListeners = true;
+ private boolean checkCanCloseForAllListeners(@NonNull String cameraId) {
for (int i = 0; i < mCameraStateListeners.size(); i++) {
- closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId);
+ if (!mCameraStateListeners.get(i).canCameraBeClosed(cameraId)) {
+ return false;
+ }
}
+ return true;
+ }
- return closeSuccessfulForAllListeners;
+ private void notifyListenersCameraClosed() {
+ for (int i = 0; i < mCameraStateListeners.size(); i++) {
+ mCameraStateListeners.get(i).onCameraClosed();
+ }
}
// TODO(b/335165310): verify that this works in multi instance and permission dialogs.
@@ -299,14 +308,18 @@ class CameraStateMonitor {
/**
* Notifies the compat listener that an activity has opened camera.
*/
- // TODO(b/336474959): try to decouple `cameraId` from the listeners.
- void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
+ void onCameraOpened(@NonNull ActivityRecord cameraActivity);
/**
- * Notifies the compat listener that camera is closed.
+ * Checks whether a listener is ready to do a cleanup when camera is closed.
*
- * @return true if cleanup has been successful - the notifier might try again if false.
+ * <p>The notifier might try again if false is returned.
*/
// TODO(b/336474959): try to decouple `cameraId` from the listeners.
- boolean onCameraClosed(@NonNull String cameraId);
+ boolean canCameraBeClosed(@NonNull String cameraId);
+
+ /**
+ * Notifies the compat listener that camera is closed.
+ */
+ void onCameraClosed();
}
}
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 0acc6610a218..d291d99f2a7a 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.app.IApplicationThread;
import android.app.compat.CompatChanges;
-import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.LaunchActivityItem;
@@ -97,31 +96,34 @@ class ClientLifecycleManager {
}
/**
- * Schedules a single transaction item, either a callback or a lifecycle request, delivery to
- * client application.
+ * Schedules a transaction with the given item, delivery to client application.
+ *
* @throws RemoteException
* @see ClientTransactionItem
*/
void scheduleTransactionItem(@NonNull IApplicationThread client,
- @NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ @NonNull ClientTransactionItem item) throws RemoteException {
// Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
// transactions at once.
final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
- clientTransaction.addTransactionItem(transactionItem);
+ clientTransaction.addTransactionItem(item);
- onClientTransactionItemScheduled(clientTransaction,
- false /* shouldDispatchImmediately */);
+ onClientTransactionItemScheduled(clientTransaction, false /* shouldDispatchImmediately */);
}
- void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
- @NonNull ClientTransactionItem transactionItem,
- @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
- scheduleTransactionAndLifecycleItems(client, transactionItem, lifecycleItem,
- false /* shouldDispatchImmediately */);
+ /**
+ * Schedules a transaction with the given items, delivery to client application.
+ *
+ * @throws RemoteException
+ * @see ClientTransactionItem
+ */
+ void scheduleTransactionItems(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem... items) throws RemoteException {
+ scheduleTransactionItems(client, false /* shouldDispatchImmediately */, items);
}
/**
- * Schedules a single transaction item with a lifecycle request, delivery to client application.
+ * Schedules a transaction with the given items, delivery to client application.
*
* @param shouldDispatchImmediately whether or not to dispatch the transaction immediately. This
* should only be {@code true} when it is important to know the
@@ -133,15 +135,17 @@ class ClientLifecycleManager {
* @throws RemoteException
* @see ClientTransactionItem
*/
- void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
- @NonNull ClientTransactionItem transactionItem,
- @NonNull ActivityLifecycleItem lifecycleItem,
- boolean shouldDispatchImmediately) throws RemoteException {
+ void scheduleTransactionItems(@NonNull IApplicationThread client,
+ boolean shouldDispatchImmediately,
+ @NonNull ClientTransactionItem... items) throws RemoteException {
// Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
// transactions at once.
final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
- clientTransaction.addTransactionItem(transactionItem);
- clientTransaction.addTransactionItem(lifecycleItem);
+
+ final int size = items.length;
+ for (int i = 0; i < size; i++) {
+ clientTransaction.addTransactionItem(items[i]);
+ }
onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 44202a2ae58e..a63ae2e1b67e 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index d20a04ab909b..e05a87d2ceb5 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -23,7 +23,7 @@ import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.ViewProtoEnums.DISPLAY_STATE_OFF;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -243,6 +243,19 @@ final class ContentRecorder implements WindowContainerListener {
}
}
+ /** Called when the surface of display is changed to a different instance. */
+ void resetRecordingDisplay(int displayId) {
+ if (!isCurrentlyRecording()
+ || mContentRecordingSession.getDisplayToRecord() != displayId) {
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Display %d changed surface so stop recording", displayId);
+ mDisplayContent.mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply();
+ mRecordedSurface = null;
+ // Do not un-set the token, in case new surface is ready and recording should begin again.
+ }
+
/**
* Pauses recording on this display content. Note the session does not need to be updated,
* since recording can be resumed still.
@@ -287,6 +300,11 @@ final class ContentRecorder implements WindowContainerListener {
}
}
+ private boolean isDisplayReadyForMirroring() {
+ return mDisplayContent.getDisplayInfo().type != Display.TYPE_EXTERNAL
+ || mDisplayContent.mWmService.mDisplayManagerInternal.isDisplayReadyForMirroring(
+ mDisplayContent.getDisplayId());
+ }
/**
* Ensure recording does not fall back to the display stack; ensure the recording is stopped
@@ -337,7 +355,7 @@ final class ContentRecorder implements WindowContainerListener {
return;
}
- if (mContentRecordingSession.isWaitingForConsent()) {
+ if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
+ "nothing");
return;
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index 283f819fc6ae..dd7675fb6442 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 3a2cffbe1d85..37e8f6260420 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.view.WindowManager.TRANSIT_CHANGE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
@@ -401,6 +401,9 @@ class DeferredDisplayUpdater {
|| !Objects.equals(first.deviceProductInfo, second.deviceProductInfo)
|| first.modeId != second.modeId
|| first.renderFrameRate != second.renderFrameRate
+ || first.hasArrSupport != second.hasArrSupport
+ || !Objects.equals(first.frameRateCategoryRate, second.frameRateCategoryRate)
+ || !Arrays.equals(first.supportedRefreshRates, second.supportedRefreshRates)
|| first.defaultModeId != second.defaultModeId
|| first.userPreferredModeId != second.userPreferredModeId
|| !Arrays.equals(first.supportedModes, second.supportedModes)
@@ -421,6 +424,7 @@ class DeferredDisplayUpdater {
|| first.brightnessMinimum != second.brightnessMinimum
|| first.brightnessMaximum != second.brightnessMaximum
|| first.brightnessDefault != second.brightnessDefault
+ || first.brightnessDim != second.brightnessDim
|| first.installOrientation != second.installOrientation
|| first.isForceSdr != second.isForceSdr
|| !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 192469183a54..43855aa3d247 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -188,22 +188,19 @@ public class DesktopAppCompatAspectRatioPolicy {
}
final ActivityInfo info = mActivityRecord.info;
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
- }
-
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (shouldApplyUserMinAspectRatioOverride(task)) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final DisplayContent dc = task.mDisplayContent;
- final boolean shouldOverrideMinAspectRatioForCamera = dc != null
- && dc.mAppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
- && !shouldOverrideMinAspectRatioForCamera) {
- return info.getMinAspectRatio();
+ && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
+ final float minAspectRatio = info.getMinAspectRatio();
+ if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
+ return minAspectRatio;
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -246,7 +243,11 @@ public class DesktopAppCompatAspectRatioPolicy {
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
- return mActivityRecord.info.getMaxAspectRatio();
+ final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+ if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
+ return maxAspectRatio;
}
/**
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 34b5f6a24d41..fcf88d395f1c 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -30,14 +30,13 @@ import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
-import android.app.TaskInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.ActivityInfo.WindowLayout;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.Size;
import android.view.Gravity;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import java.util.function.Consumer;
@@ -98,7 +97,6 @@ public final class DesktopModeBoundsCalculator {
private static Rect calculateInitialBounds(@NonNull Task task,
@NonNull ActivityRecord activity, @NonNull Rect stableBounds
) {
- final TaskInfo taskInfo = task.getTaskInfo();
// Display bounds not taking into account insets.
final TaskDisplayArea displayArea = task.getDisplayArea();
final Rect screenBounds = displayArea.getBounds();
@@ -118,14 +116,15 @@ public final class DesktopModeBoundsCalculator {
float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
final float tdaWidth = stableBounds.width();
final float tdaHeight = stableBounds.height();
+ final int taskConfigOrientation = task.getConfiguration().orientation;
final int activityOrientation = getActivityOrientation(activity, task);
- final Size initialSize = switch (taskInfo.configuration.orientation) {
+ final Size initialSize = switch (taskConfigOrientation) {
case ORIENTATION_LANDSCAPE -> {
// Device in landscape orientation.
if (appAspectRatio == 0) {
appAspectRatio = 1;
}
- if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
+ if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
if (isFixedOrientationPortrait(activityOrientation)) {
// For portrait resizeable activities, respect apps fullscreen width but
// apply ideal size height.
@@ -143,7 +142,7 @@ public final class DesktopModeBoundsCalculator {
// Device in portrait orientation.
final int customPortraitWidthForLandscapeApp = screenBounds.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
- if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
+ if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
if (isFixedOrientationLandscape(activityOrientation)) {
if (appAspectRatio == 0) {
appAspectRatio = tdaWidth / (tdaWidth - 1);
@@ -182,8 +181,8 @@ public final class DesktopModeBoundsCalculator {
*/
private static boolean canChangeAspectRatio(
@NonNull DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy,
- @NonNull TaskInfo taskInfo, @NonNull Task task) {
- return taskInfo.isResizeable
+ @NonNull Task task) {
+ return task.isResizeable()
&& !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task);
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index b5ea0bdfc27a..6bf1c466aeb5 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -19,7 +19,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.content.Context;
import android.os.SystemProperties;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 4940d2ec9c3a..25fdf89afad1 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -16,9 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DIMMER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,7 +42,8 @@ class Dimmer {
*/
private final WindowContainer<?> mHost;
- private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ private static final String TAG = "WindowManagerDimmer";
+
DimState mDimState;
final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
@@ -69,9 +68,10 @@ class Dimmer {
DimState() {
mHostContainer = mHost;
- mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
+ mAnimationHelper = new DimmerAnimationHelper(mHost, mAnimationAdapterFactory);
try {
mDimSurface = makeDimLayer();
+ EventLogTags.writeWmDimCreated(mHost.getName(), mDimSurface.getLayerId());
} catch (Surface.OutOfResourcesException e) {
Log.w(TAG, "OutOfResourcesException creating dim surface");
}
@@ -102,6 +102,11 @@ class Dimmer {
* Prepare the dim for the exit animation
*/
void exit(@NonNull SurfaceControl.Transaction t) {
+ EventLogTags.writeWmDimExit(mDimState.mDimSurface.getLayerId(),
+ mDimState.mLastDimmingWindow != null
+ ? mDimState.mLastDimmingWindow.getName() : "-",
+ mDimState.mHostContainer.isVisible() ? 1 : 0,
+ mAnimateExit ? 0 : 1);
if (!mAnimateExit) {
remove(t);
} else {
@@ -111,8 +116,10 @@ class Dimmer {
}
void remove(@NonNull SurfaceControl.Transaction t) {
+ EventLogTags.writeWmDimCancelAnim(mDimSurface.getLayerId(), "ready to remove");
mAnimationHelper.stopCurrentAnimation(mDimSurface);
if (mDimSurface.isValid()) {
+ EventLogTags.writeWmDimRemoved(mDimSurface.getLayerId());
t.remove(mDimSurface);
ProtoLog.d(WM_DEBUG_DIMMER,
"Removing dim surface %s on transaction %s", this, t);
@@ -126,6 +133,13 @@ class Dimmer {
return "Dimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
}
+
+ String reasonForRemoving() {
+ return mLastDimmingWindow != null ? mLastDimmingWindow
+ + " is dimming but host " + mHostContainer + " is not visibleRequested"
+ : " no one is dimming";
+ }
+
/**
* Set the parameters to prepare the dim to be relative parented to the dimming container
*/
@@ -261,6 +275,14 @@ class Dimmer {
}
}
+ boolean hasDimState() {
+ return mDimState != null;
+ }
+
+ boolean isDimming() {
+ return mDimState != null && mDimState.isDimming();
+ }
+
@NonNull
private DimState obtainDimState(@NonNull WindowState window) {
if (mDimState == null) {
@@ -276,7 +298,6 @@ class Dimmer {
return mDimState != null ? mDimState.mDimSurface : null;
}
- @Deprecated
Rect getDimBounds() {
return mDimState != null ? mDimState.mDimBounds : null;
}
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 478bdfeffe79..1d447dd692df 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DIMMER;
import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
import static com.android.server.wm.AlphaAnimationSpecProto.TO;
@@ -27,11 +27,13 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -74,9 +76,11 @@ public class DimmerAnimationHelper {
return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer;
}
- void inheritPropertiesFromAnimation(@NonNull AnimationSpec anim) {
- mAlpha = anim.mCurrentAlpha;
- mBlurRadius = anim.mCurrentBlur;
+ void inheritPropertiesFromAnimation(@Nullable AnimationSpec anim) {
+ if (anim != null) {
+ mAlpha = anim.mCurrentAlpha;
+ mBlurRadius = anim.mCurrentBlur;
+ }
}
@Override
@@ -90,11 +94,13 @@ public class DimmerAnimationHelper {
private final Change mRequestedProperties = new Change();
private AnimationSpec mAlphaAnimationSpec;
+ private final SurfaceAnimationRunner mSurfaceAnimationRunner;
private final AnimationAdapterFactory mAnimationAdapterFactory;
private AnimationAdapter mLocalAnimationAdapter;
- DimmerAnimationHelper(AnimationAdapterFactory animationFactory) {
+ DimmerAnimationHelper(WindowContainer<?> host, AnimationAdapterFactory animationFactory) {
mAnimationAdapterFactory = animationFactory;
+ mSurfaceAnimationRunner = host.mWmService.mSurfaceAnimationRunner;
}
void setExitParameters() {
@@ -153,8 +159,12 @@ public class DimmerAnimationHelper {
? mRequestedProperties.mGeometryParent.getSurfaceControl() : null,
mRequestedProperties.mDimmingContainer != startProperties.mDimmingContainer
? mRequestedProperties.mDimmingContainer.getSurfaceControl() : null, t);
+ if (Flags.useTasksDimOnly()) {
+ setBounds(dim, mCurrentProperties.mDimmingContainer, t);
+ }
if (!startProperties.hasSameVisualProperties(mRequestedProperties)) {
+ EventLogTags.writeWmDimCancelAnim(dim.mDimSurface.getLayerId(), "new target values");
stopCurrentAnimation(dim.mDimSurface);
if (dim.mSkipAnimation
@@ -184,13 +194,15 @@ public class DimmerAnimationHelper {
ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
mAlphaAnimationSpec = getRequestedAnimationSpec(from, to);
mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
- dim.mHostContainer.mWmService.mSurfaceAnimationRunner);
+ mSurfaceAnimationRunner);
float targetAlpha = to.mAlpha;
+ EventLogTags.writeWmDimAnimate(dim.mDimSurface.getLayerId(), targetAlpha, to.mBlurRadius);
mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t,
ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> {
synchronized (dim.mHostContainer.mWmService.mGlobalLock) {
+ EventLogTags.writeWmDimFinishAnim(dim.mDimSurface.getLayerId());
SurfaceControl.Transaction finishTransaction =
dim.mHostContainer.getSyncTransaction();
setCurrentAlphaBlur(dim, finishTransaction);
@@ -203,18 +215,12 @@ public class DimmerAnimationHelper {
});
}
- private boolean isAnimating() {
- return mAlphaAnimationSpec != null;
- }
-
void stopCurrentAnimation(@NonNull SurfaceControl surface) {
- if (mLocalAnimationAdapter != null && isAnimating()) {
- // Save the current animation progress and cancel the animation
- mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
- mLocalAnimationAdapter.onAnimationCancelled(surface);
- mLocalAnimationAdapter = null;
- mAlphaAnimationSpec = null;
- }
+ // (If animating) save the current animation progress and cancel the animation
+ mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
+ mSurfaceAnimationRunner.onAnimationCancelled(surface);
+ mLocalAnimationAdapter = null;
+ mAlphaAnimationSpec = null;
}
@NonNull
@@ -253,6 +259,32 @@ public class DimmerAnimationHelper {
}
}
+ static void setBounds(@NonNull Dimmer.DimState dim, @NonNull WindowState relativeParent,
+ @NonNull SurfaceControl.Transaction t) {
+ TaskFragment taskFragment = relativeParent.getTaskFragment();
+ Rect taskFragmentBounds = taskFragment != null ? taskFragment.getBounds() : null;
+ Task task = relativeParent.getTask();
+ Rect taskBounds = task != null ? task.getBounds() : null;
+ Rect hostBounds = dim.mHostContainer.getBounds();
+ boolean isEmbedded = taskFragment != null && taskFragment.isEmbedded();
+
+ Rect relativeBounds = new Rect();
+ if (isEmbedded) {
+ // Embedded activities can be dimmed at task or fragment level
+ dim.mDimBounds.set(taskFragment.isDimmingOnParentTask()
+ ? taskBounds : taskFragmentBounds);
+ relativeBounds.set(dim.mDimBounds);
+ relativeBounds.offset(-taskBounds.left, -taskBounds.top);
+ } else {
+ dim.mDimBounds.set(hostBounds);
+ relativeBounds.set(dim.mDimBounds);
+ relativeBounds.offsetTo(0, 0);
+ }
+
+ t.setWindowCrop(dim.mDimSurface, relativeBounds.width(), relativeBounds.height());
+ t.setPosition(dim.mDimSurface, relativeBounds.left, relativeBounds.top);
+ }
+
void setCurrentAlphaBlur(@NonNull Dimmer.DimState dim, @NonNull SurfaceControl.Transaction t) {
final SurfaceControl sc = dim.mDimSurface;
try {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index ca5485e7c570..f40d636b522a 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -24,7 +23,7 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.wm.DisplayAreaProto.FEATURE_ID;
import static com.android.server.wm.DisplayAreaProto.IS_IGNORING_ORIENTATION_REQUEST;
@@ -47,6 +46,7 @@ import android.window.IDisplayAreaOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.Comparator;
@@ -54,6 +54,7 @@ import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
+
/**
* Container for grouping WindowContainer below DisplayContent.
*
@@ -100,8 +101,6 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
DisplayArea(WindowManagerService wms, Type type, String name, int featureId) {
super(wms);
- // TODO(display-area): move this up to ConfigurationContainer
- setOverrideOrientation(SCREEN_ORIENTATION_UNSET);
mType = type;
mName = name;
mFeatureId = featureId;
@@ -260,15 +259,14 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
if (mDisplayContent == null) {
return false;
}
- ActivityRecord activity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- return activity != null && activity.getTaskFragment() != null
- // Checking TaskFragment rather than ActivityRecord to ensure that transition
- // between fullscreen and PiP would work well. Checking TaskFragment rather than
- // Task to ensure that Activity Embedding is excluded.
- && activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && activity.mAppCompatController.getAppCompatOrientationOverrides()
- .isOverrideRespectRequestedOrientationEnabled();
+
+ // Top running activity can be freeform and ignore orientation request from bottom activity
+ // that should be respected, Check all activities in display to make sure any eligible
+ // activity should be respected.
+ final ActivityRecord activity = mDisplayContent.getActivity((r) ->
+ r.mAppCompatController.getAppCompatOrientationOverrides()
+ .shouldRespectRequestedOrientationDueToOverride());
+ return activity != null;
}
boolean getIgnoreOrientationRequest() {
@@ -833,11 +831,14 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
void prepareSurfaces() {
mDimmer.resetDimStates();
super.prepareSurfaces();
- final Rect dimBounds = mDimmer.getDimBounds();
- if (dimBounds != null) {
- // Bounds need to be relative, as the dim layer is a child.
- getBounds(dimBounds);
- dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ Rect dimBounds = null;
+ if (!Flags.useTasksDimOnly()) {
+ dimBounds = mDimmer.getDimBounds();
+ if (dimBounds != null) {
+ // Bounds need to be relative, as the dim layer is a child.
+ getBounds(dimBounds);
+ dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ }
}
// If SystemUI is dragging for recents, we want to reset the dim state so any dim layer
@@ -847,7 +848,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
mDimmer.resetDimStates();
}
- if (dimBounds != null) {
+ if (mDimmer.hasDimState()) {
if (mDimmer.updateDims(getSyncTransaction())) {
scheduleAnimation();
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index 8f471d797904..afeeaf707a18 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.window.DisplayAreaOrganizer.FEATURE_RUNTIME_TASK_CONTAINER_FIRST;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.DisplayArea.Type.ANY;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index bb596cc829c9..8a2b8b65a935 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -29,7 +29,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWING_LAYER;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORGANIZER;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fa97a1d43dc5..2f646fbde35a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -88,17 +88,17 @@ import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_KEEP_SCREEN_ON;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -159,7 +159,6 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -261,6 +260,7 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.RegionUtils;
import com.android.server.wm.utils.RotationCache;
import com.android.server.wm.utils.WmDisplayCutout;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -792,6 +792,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private WindowState mLastWakeLockHoldingWindow;
/**
+ * Whether display is allowed to ignore all activity size restrictions.
+ * @see #isDisplayIgnoreActivitySizeRestrictions
+ */
+ private final boolean mIgnoreActivitySizeRestrictions;
+
+ /**
* The helper of policy controller.
*
* @see DisplayWindowPolicyControllerHelper
@@ -1222,6 +1228,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+ mIgnoreActivitySizeRestrictions =
+ mWmService.mDisplayWindowSettings.isIgnoreActivitySizeRestrictionsLocked(this);
// Sets the initial touch mode state.
mInTouchMode = mWmService.mContext.getResources().getBoolean(
@@ -1266,7 +1274,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
-
+ // Reset the recording displays which were mirroring this display.
+ for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+ final ContentRecorder recorder = mRootWindowContainer.getChildAt(i).mContentRecorder;
+ if (recorder != null) {
+ recorder.resetRecordingDisplay(mDisplayId);
+ }
+ }
mLastSurfacePosition.set(0, 0);
mLastDeltaRotation = Surface.ROTATION_0;
@@ -1378,11 +1392,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTokenMap.put(binder, token);
if (token.asActivityRecord() == null) {
- // Set displayContent for non-app token to prevent same token will add twice after
- // onDisplayChanged.
- // TODO: Check if it's fine that super.onDisplayChanged of WindowToken
- // (WindowsContainer#onDisplayChanged) may skipped when token.mDisplayContent assigned.
- token.mDisplayContent = this;
+ // Setting the mDisplayContent to the token is not needed: it is done by da.addChild
+ // below, that also calls onDisplayChanged once moved.
+ if (!Flags.reparentWindowTokenApi()) {
+ // Set displayContent for non-app token to prevent same token will add twice after
+ // onDisplayChanged.
+ // TODO: Check if it's fine that super.onDisplayChanged of WindowToken
+ // (WindowsContainer#onDisplayChanged) may skipped when token.mDisplayContent
+ // assigned.
+ token.mDisplayContent = this;
+ }
// Add non-app token to container hierarchy on the display. App tokens are added through
// the parent container managing them (e.g. Tasks).
final DisplayArea.Tokens da = findAreaForToken(token).asTokens();
@@ -2268,7 +2287,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
- getPendingTransaction().setFixedTransformHint(mSurfaceControl, rotation);
+ setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
// The sync transaction should already contains setDisplayProjection, so unset the
// hint to restore the natural state when the transaction is applied.
transaction.unsetFixedTransformHint(mSurfaceControl);
@@ -2278,6 +2297,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
}
+ void setFixedTransformHint(Transaction t, SurfaceControl sc, int rotation) {
+ t.setFixedTransformHint(sc, (rotation + mDisplayInfo.installOrientation) % 4);
+ }
+
void configureDisplayPolicy() {
mRootWindowContainer.updateDisplayImePolicyCache();
mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
@@ -3427,14 +3450,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!mWmService.mSupportsHighPerfTransitions) {
return;
}
- if (!explicitRefreshRateHints()) {
- if (enable) {
- getPendingTransaction().setEarlyWakeupStart();
- } else {
- getPendingTransaction().setEarlyWakeupEnd();
- }
- return;
- }
if (enable) {
if (mTransitionPrefSession == null) {
mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -3447,10 +3462,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
void enableHighFrameRate(boolean enable) {
- if (!explicitRefreshRateHints()) {
- // Done by RefreshRatePolicy.
- return;
- }
if (enable) {
if (mHighFrameRateSession == null) {
mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -4280,7 +4291,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
final int imePolicy = mWmService.mDisplayWindowSettings.getImePolicyLocked(this);
- if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY && forceDesktopMode()) {
+ if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY
+ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
// If the display has not explicitly requested for the IME to be hidden then it shall
// show the IME locally.
return DISPLAY_IME_POLICY_LOCAL;
@@ -4288,10 +4300,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return imePolicy;
}
- boolean forceDesktopMode() {
- return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
- }
-
/** @see WindowManagerInternal#onToggleImeRequested */
void onShowImeRequested() {
if (mInputMethodWindow == null) {
@@ -4884,7 +4892,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** @return {@code true} if there is window to wait before enabling the screen. */
boolean shouldWaitForSystemDecorWindowsOnBoot() {
- if (!isDefaultDisplay && !supportsSystemDecorations()) {
+ if (!isDefaultDisplay && !isSystemDecorationsSupported()) {
// Nothing to wait because the secondary display doesn't support system decorations,
// there is no wallpaper, keyguard (status bar) or application (home) window to show
// during booting.
@@ -5515,14 +5523,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Attach the SystemUiContext to this DisplayContent the get latest configuration.
// Note that the SystemUiContext will be removed automatically if this DisplayContent
// is detached.
- final WindowProcessController wpc = mAtmService.getProcessController(
- getDisplayUiContext().getIApplicationThread());
- mWmService.mWindowContextListenerController.registerWindowContainerListener(
- wpc, getDisplayUiContext().getWindowContextToken(), this,
- INVALID_WINDOW_TYPE, null /* options */);
+ registerSystemUiContext();
}
}
+ private void registerSystemUiContext() {
+ final WindowProcessController wpc = mAtmService.getProcessController(
+ getDisplayUiContext().getIApplicationThread());
+ mWmService.mWindowContextListenerController.registerWindowContainerListener(
+ wpc, getDisplayUiContext().getWindowContextToken(), this,
+ INVALID_WINDOW_TYPE, null /* options */);
+ }
+
@Override
void assignChildLayers(SurfaceControl.Transaction t) {
assignRelativeLayerForIme(t, false /* forceUpdate */);
@@ -5759,22 +5771,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
*/
- boolean supportsSystemDecorations() {
- boolean forceDesktopModeOnDisplay = forceDesktopMode();
-
- if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
- // System decorations should not be forced on a rear display due to security policies.
- forceDesktopModeOnDisplay =
- forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+ boolean isSystemDecorationsSupported() {
+ if (mDisplayId == mWmService.mVr2dDisplayId) {
+ // VR virtual display will be used to run and render 2D app within a VR experience.
+ return false;
}
+ if (!isTrusted()) {
+ // Do not show system decorations on untrusted virtual display.
+ return false;
+ }
+ if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
+ || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+ // This display is configured to show system decorations.
+ return true;
+ }
+ if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
+ if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+ // System decorations should not be forced on a rear display due to security
+ // policies.
+ return (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+ }
+ // If the display is forced to desktop mode, treat it the same as it is configured to
+ // show system decorations.
+ return true;
+ }
+ return false;
+ }
- return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
- || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
- || forceDesktopModeOnDisplay)
- // VR virtual display will be used to run and render 2D app within a VR experience.
- && mDisplayId != mWmService.mVr2dDisplayId
- // Do not show system decorations on untrusted virtual display.
- && isTrusted();
+ /**
+ * This is the development option to force enable desktop mode on all secondary public displays
+ * that are not owned by a virtual device.
+ * When this is enabled, it also force enable system decorations on those displays.
+ *
+ * If we need a per-display config to enable desktop mode for production, that config should
+ * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
+ */
+ boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
+ if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
+ return false;
+ }
+ // Desktop mode is not supported on virtual devices.
+ int deviceId = mRootWindowContainer.mTaskSupervisor.getDeviceIdForDisplayId(mDisplayId);
+ return deviceId == Context.DEVICE_ID_DEFAULT;
}
/**
@@ -5785,7 +5823,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
boolean isHomeSupported() {
return (mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) && isTrusted())
- || supportsSystemDecorations();
+ || isSystemDecorationsSupported();
+ }
+
+ /**
+ * Checks if this display is allowed to ignore fixed orientation, aspect ratio,
+ * and resizability of apps.
+ *
+ * <p>This can be set via
+ * {@link VirtualDisplayConfig.Builder#setIgnoreActivitySizeRestrictions}.</p>
+ */
+ boolean isDisplayIgnoreActivitySizeRestrictions() {
+ return mIgnoreActivitySizeRestrictions;
}
/**
@@ -6923,27 +6972,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return;
}
if (mFixedRotationLaunchingApp.hasFixedRotationTransform(r)) {
+ if (!r.isVisible()) {
+ // Let the opening activity update orientation.
+ return;
+ }
if (mFixedRotationLaunchingApp.hasAnimatingFixedRotationTransition()) {
// Waiting until all of the associated activities have done animation, or the
// orientation would be updated too early and cause flickering.
return;
}
} else {
- // Handle a corner case that the task of {@link #mFixedRotationLaunchingApp} is no
- // longer animating but the corresponding transition finished event won't notify.
- // E.g. activity A transferred starting window to B, only A will receive transition
- // finished event. A doesn't have fixed rotation but B is the rotated launching app.
- final Task task = r.getTask();
- if (task != mFixedRotationLaunchingApp.getTask()
+ // Check to skip updating display orientation by a non-top activity.
+ if ((!r.isVisible() || !mFixedRotationLaunchingApp.fillsParent())
// When closing a translucent task A (r.fillsParent() is false) to a
// visible task B, because only A has visibility change, there is only A's
// transition callback. Then it still needs to update orientation for B.
- && (!mWmService.mFlags.mRespectNonTopVisibleFixedOrientation
- || r.fillsParent())) {
- // Different tasks won't be in one activity transition animation.
+ && r.fillsParent()) {
return;
}
- if (task.getActivity(ActivityRecord::isInTransition) != null) {
+ if (r.inTransition()) {
return;
// Continue to update orientation because the transition of the top rotated
// launching activity is done.
@@ -7061,12 +7108,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void setImeInputTargetRequestedVisibility(boolean visible) {
+ public void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
+ // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
try {
- mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
+ mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible,
+ statsToken);
} catch (RemoteException e) {
+ // TODO(b/353463205) fail statsToken
Slog.w(TAG, "Failed to deliver setImeInputTargetRequestedVisibility", e);
}
}
@@ -7075,9 +7125,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* @see #getRequestedVisibleTypes()
*/
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
- if (mRequestedVisibleTypes != requestedVisibleTypes) {
- mRequestedVisibleTypes = requestedVisibleTypes;
+ void updateRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+ int newRequestedVisibleTypes =
+ (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
+ if (mRequestedVisibleTypes != newRequestedVisibleTypes) {
+ mRequestedVisibleTypes = newRequestedVisibleTypes;
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 0fa1a2138e35..659bb6784c89 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE;
import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS;
@@ -65,20 +64,17 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.window.flags.Flags.enableFullyImmersiveInDesktop;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -108,7 +104,6 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayInfo;
-import android.view.Gravity;
import android.view.InsetsFlags;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
@@ -141,7 +136,6 @@ import com.android.internal.view.AppearanceRegion;
import com.android.internal.widget.PointerLocationView;
import com.android.server.LocalServices;
import com.android.server.UiThread;
-import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -262,8 +256,7 @@ public class DisplayPolicy {
private WindowState mStatusBar = null;
private volatile WindowState mNotificationShade;
private WindowState mNavigationBar = null;
- @NavigationBarPosition
- private int mNavigationBarPosition = NAV_BAR_BOTTOM;
+ private boolean mHasBottomNavigationBar = true;
private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
@@ -666,7 +659,7 @@ public class DisplayPolicy {
}
} else {
mHasStatusBar = false;
- mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
+ mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
}
mRefreshRatePolicy = new RefreshRatePolicy(mService,
@@ -1073,7 +1066,8 @@ public class DisplayPolicy {
final String systemUiPermission =
mService.isCallerVirtualDeviceOwner(mDisplayContent.getDisplayId(), callingUid)
- // Allow virtual device owners to add system windows on their displays.
+ && mDisplayContent.isTrusted()
+ // Virtual device owners can add system windows on their trusted displays.
? android.Manifest.permission.CREATE_VIRTUAL_DEVICE
: android.Manifest.permission.STATUS_BAR_SERVICE;
@@ -1253,18 +1247,7 @@ public class DisplayPolicy {
throw new IllegalArgumentException("IME insets must be provided by a window.");
}
- if (!ENABLE_HIDE_IME_CAPTION_BAR && mNavigationBar != null
- && navigationBarPosition(displayFrames.mRotation) == NAV_BAR_BOTTOM) {
- // In gesture navigation, nav bar frame is larger than frame to calculate insets.
- // IME should not provide frame which is smaller than the nav bar frame. Otherwise,
- // nav bar might be overlapped with the content of the client when IME is shown.
- sTmpRect.set(inOutFrame);
- sTmpRect.intersectUnchecked(mNavigationBar.getFrame());
- inOutFrame.inset(windowState.mGivenContentInsets);
- inOutFrame.union(sTmpRect);
- } else {
- inOutFrame.inset(windowState.mGivenContentInsets);
- }
+ inOutFrame.inset(windowState.mGivenContentInsets);
return 0;
};
}
@@ -1467,10 +1450,9 @@ public class DisplayPolicy {
public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
WindowState attached, WindowState imeTarget) {
if (attrs.type == TYPE_NAVIGATION_BAR) {
- // Keep mNavigationBarPosition updated to make sure the transient detection and bar
- // color control is working correctly.
- final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
- mNavigationBarPosition = navigationBarPosition(displayFrames.mRotation);
+ // Keep mHasBottomNavigationBar updated to make sure the bar color control is working
+ // correctly.
+ mHasBottomNavigationBar = hasBottomNavigationBar();
}
final boolean affectsSystemUi = win.canAffectSystemUiFlags();
if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
@@ -1753,9 +1735,9 @@ public class DisplayPolicy {
}
// Show IME over the keyguard if the target allows it.
- final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
- && win.mIsImWindow && (imeTarget.canShowWhenLocked()
- || !imeTarget.canBeHiddenByKeyguard());
+ final boolean showImeOverKeyguard =
+ imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && (
+ imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
if (showImeOverKeyguard) {
return false;
}
@@ -2131,7 +2113,8 @@ public class DisplayPolicy {
final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
- if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
+ final boolean sameConfigFrame = newInfo.mConfigFrame.equals(currentInfo.mConfigFrame);
+ if (sameConfigFrame
&& newInfo.mOverrideConfigFrame.equals(currentInfo.mOverrideConfigFrame)) {
// Even if the config frame is not changed in current rotation, it may change the
// insets in other rotations if the frame of insets source is changed.
@@ -2155,7 +2138,12 @@ public class DisplayPolicy {
}
mDecorInsets.invalidate();
mDecorInsets.mInfoForRotation[rotation].set(newInfo);
- return true;
+ if (!mService.mDisplayEnabled) {
+ // There could be other pending changes during booting. It might be better to let the
+ // clients receive the new states earlier.
+ return true;
+ }
+ return !sameConfigFrame;
}
DecorInsets.Info getDecorInsetsInfo(int rotation, int w, int h) {
@@ -2222,20 +2210,11 @@ public class DisplayPolicy {
mDisplayContent.mDisplayUpdater.onDisplaySwitching(true);
}
- @NavigationBarPosition
- int navigationBarPosition(int displayRotation) {
- if (mNavigationBar != null) {
- final int gravity = mNavigationBar.mAttrs.forRotation(displayRotation).gravity;
- switch (gravity) {
- case Gravity.LEFT:
- return NAV_BAR_LEFT;
- case Gravity.RIGHT:
- return NAV_BAR_RIGHT;
- default:
- return NAV_BAR_BOTTOM;
- }
- }
- return NAV_BAR_INVALID;
+ boolean hasBottomNavigationBar() {
+ Insets navBarInsets = mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .calculateInsets(mDisplayContent.mDisplayFrames.mUnrestricted,
+ Type.navigationBars(), true /* ignoreVisibilities */);
+ return navBarInsets.bottom > 0;
}
/**
@@ -2397,7 +2376,7 @@ public class DisplayPolicy {
return;
}
final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
- mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
+ mDisplayContent.mInputMethodWindow, mHasBottomNavigationBar);
final boolean isNavbarColorManagedByIme =
navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance,
@@ -2459,12 +2438,12 @@ public class DisplayPolicy {
@VisibleForTesting
@Nullable
static WindowState chooseNavigationColorWindowLw(WindowState candidate, WindowState imeWindow,
- @NavigationBarPosition int navBarPosition) {
+ boolean hasBottomNavigationBar) {
// If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME
// window can be navigation color window.
final boolean imeWindowCanNavColorWindow = imeWindow != null
&& imeWindow.isVisible()
- && navBarPosition == NAV_BAR_BOTTOM
+ && hasBottomNavigationBar
&& (imeWindow.mAttrs.flags
& WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
if (!imeWindowCanNavColorWindow) {
@@ -2508,10 +2487,16 @@ public class DisplayPolicy {
defaultTaskDisplayArea.getRootTask(task -> task.isVisible()
&& task.getTopLeafTask().getAdjacentTask() != null)
!= null;
- final boolean freeformRootTaskVisible =
- defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
+ final Task topFreeformTask = defaultTaskDisplayArea
+ .getTopRootTaskInWindowingMode(WINDOWING_MODE_FREEFORM);
+ final boolean freeformRootTaskVisible = topFreeformTask != null
+ && topFreeformTask.isVisible();
+ final boolean inNonFullscreenFreeformMode = freeformRootTaskVisible
+ && !topFreeformTask.getBounds().equals(mDisplayContent.getBounds());
- getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible);
+ getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible,
+ enableFullyImmersiveInDesktop()
+ ? inNonFullscreenFreeformMode : freeformRootTaskVisible);
final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
if (getStatusBar() != null) {
@@ -2675,7 +2660,7 @@ public class DisplayPolicy {
final WindowState navBackgroundWin = chooseNavigationBackgroundWindow(
mNavBarBackgroundWindowCandidate,
mDisplayContent.mInputMethodWindow,
- mNavigationBarPosition);
+ mHasBottomNavigationBar);
final boolean drawBackground = navBackgroundWin != null
// There is no app window showing underneath nav bar. (e.g., The screen is locked.)
// Let system windows (ex: notification shade) draw nav bar background.
@@ -2713,8 +2698,8 @@ public class DisplayPolicy {
@VisibleForTesting
@Nullable
static WindowState chooseNavigationBackgroundWindow(WindowState candidate,
- WindowState imeWindow, @NavigationBarPosition int navBarPosition) {
- if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM
+ WindowState imeWindow, boolean hasBottomNavigationBar) {
+ if (imeWindow != null && imeWindow.isVisible() && hasBottomNavigationBar
&& drawsBarBackground(imeWindow)) {
return imeWindow;
}
@@ -2892,8 +2877,8 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar);
pw.print(prefix); pw.print("mNavBarOpacityMode="); pw.println(mNavBarOpacityMode);
pw.print(prefix); pw.print("mNavigationBarCanMove="); pw.println(mNavigationBarCanMove);
- pw.print(prefix); pw.print("mNavigationBarPosition=");
- pw.println(mNavigationBarPosition);
+ pw.print(prefix); pw.print("mHasBottomNavigationBar=");
+ pw.println(mHasBottomNavigationBar);
}
if (mLeftGestureHost != null) {
pw.print(prefix); pw.print("mLeftGestureHost="); pw.println(mLeftGestureHost);
@@ -3068,7 +3053,7 @@ public class DisplayPolicy {
@InsetsType int insetsType) {
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
- if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
+ if ((source.getType() & insetsType) == 0) {
continue;
}
if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8c06cfecdc40..f53bc700de05 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -27,8 +27,8 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
import static com.android.server.wm.DisplayRotationProto.FROZEN_TO_USER_ROTATION;
@@ -295,7 +295,7 @@ public class DisplayRotation {
&& mDeviceStateController
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
- mDefaultDisplayRotationChangedCallback);
+ displayContent.getDisplayId(), mDefaultDisplayRotationChangedCallback);
}
if (isDefaultDisplay) {
@@ -445,7 +445,8 @@ public class DisplayRotation {
final boolean isTv = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
mDefaultFixedToUserRotation =
- (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
+ (isCar || isTv || mService.mIsPc
+ || mDisplayContent.isPublicSecondaryDisplayWithDesktopModeForceEnabled()
|| !mDisplayContent.shouldRotateWithContent())
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
@@ -485,6 +486,9 @@ public class DisplayRotation {
if (isDefaultDisplay) {
updateOrientationListenerLw();
}
+ } else if (mCompatPolicyForImmersiveApps != null
+ && mCompatPolicyForImmersiveApps.deferOrientationUpdate()) {
+ return false;
}
return updateRotationUnchecked(forceUpdate);
}
@@ -1656,7 +1660,8 @@ public class DisplayRotation {
void removeDefaultDisplayRotationChangedCallback() {
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
- mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback(
+ mDefaultDisplayRotationChangedCallback);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index efc38439bfcf..3c199dba565b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -29,7 +29,8 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Display.TYPE_INTERNAL;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
import android.annotation.NonNull;
@@ -70,6 +71,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
@NonNull
private final ActivityRefresher mActivityRefresher;
+ // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
+ // all current camera activities, especially when the camera access is switching from one app to
+ // another.
@Nullable
private Task mCameraTask;
@@ -133,6 +137,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return mLastReportedOrientation;
}
+ float getCameraCompatAspectRatio(@NonNull ActivityRecord unusedActivity) {
+ // This policy does not apply camera compat aspect ratio by default, only via overrides.
+ return MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ }
+
@ScreenOrientation
private synchronized int getOrientationInternal() {
if (!isTreatmentEnabledForDisplay()) {
@@ -271,7 +280,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
- && isCameraActive(activity, /* mustBeFullscreen */ true)
+ && isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen */ true)
&& activity.mAppCompatController.getAppCompatCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -290,7 +299,17 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
}
- boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ return isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen= */ true);
+ }
+
+ boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord unusedActivity) {
+ // This policy does not apply camera compat aspect ratio by default, only via overrides.
+ return false;
+ }
+
+ boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity,
+ boolean mustBeFullscreen) {
// Checking windowing mode on activity level because we don't want to
// apply treatment in case of activity embedding.
return (!mustBeFullscreen || !activity.inMultiWindowMode())
@@ -299,7 +318,8 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
boolean mustBeFullscreen) {
- return activity != null && isCameraActive(activity, mustBeFullscreen)
+ return activity != null
+ && isCameraRunningAndWindowingModeEligible(activity, mustBeFullscreen)
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
@@ -310,8 +330,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
@Override
- public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
mCameraTask = cameraActivity.getTask();
// Checking whether an activity in fullscreen rather than the task as this camera
// compat treatment doesn't cover activity embedding.
@@ -357,16 +376,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
@Override
- public boolean onCameraClosed(@NonNull String cameraId) {
- final ActivityRecord topActivity;
- if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
- topActivity = mCameraTask != null ? mCameraTask.getTopActivity(
- /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
- } else {
- topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
- }
+ public boolean canCameraBeClosed(@NonNull String cameraId) {
+ final ActivityRecord topActivity = getTopActivity();
- mCameraTask = null;
if (topActivity == null) {
return true;
}
@@ -382,6 +394,23 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return false;
}
}
+ return true;
+ }
+
+ @Override
+ public void onCameraClosed() {
+ final ActivityRecord topActivity = getTopActivity();
+
+ // Only clean up if the camera is not running - this close signal could be from switching
+ // cameras (e.g. back to front camera, and vice versa).
+ if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
+ // Call after getTopActivity(), as that method might use the activity from mCameraTask.
+ mCameraTask = null;
+ }
+
+ if (topActivity == null) {
+ return;
+ }
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera is closed, updating rotation.",
@@ -389,11 +418,10 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
// Checking whether an activity in fullscreen rather than the task as this camera compat
// treatment doesn't cover activity embedding.
if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
- return true;
+ return;
}
recomputeConfigurationForCameraCompatIfNeeded(topActivity);
mDisplayContent.updateOrientation();
- return true;
}
// TODO(b/336474959): Do we need cameraId here?
@@ -413,6 +441,16 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
}
+ @Nullable
+ private ActivityRecord getTopActivity() {
+ if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
+ return mCameraTask != null ? mCameraTask.getTopActivity(
+ /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
+ } else {
+ return mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
+ }
+ }
+
/**
* @return {@code true} if the configuration needs to be recomputed after a camera state update.
*/
@@ -428,6 +466,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
private boolean shouldOverrideMinAspectRatio(@NonNull ActivityRecord activityRecord) {
return activityRecord.mAppCompatController.getAppCompatCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled()
- && isCameraActive(activityRecord, /* mustBeFullscreen= */ true);
+ && isCameraRunningAndWindowingModeEligible(activityRecord,
+ /* mustBeFullscreen= */ true);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
index ae3787cffa23..01e1b1342989 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import android.view.Display;
import android.view.Surface;
@@ -40,6 +41,7 @@ class DisplayRotationCoordinator {
@Nullable
@VisibleForTesting
Runnable mDefaultDisplayRotationChangedCallback;
+ private int mCallbackDisplayId = Display.INVALID_DISPLAY;
@Surface.Rotation
private int mDefaultDisplayCurrentRotation;
@@ -68,12 +70,15 @@ class DisplayRotationCoordinator {
* Register a callback to be notified when the default display's rotation changes. Clients can
* query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
*/
- void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
- if (mDefaultDisplayRotationChangedCallback != null) {
- throw new UnsupportedOperationException("Multiple clients unsupported");
+ void setDefaultDisplayRotationChangedCallback(int displayId, @NonNull Runnable callback) {
+ if (mDefaultDisplayRotationChangedCallback != null && displayId != mCallbackDisplayId) {
+ throw new UnsupportedOperationException("Multiple clients unsupported"
+ + ". Incoming displayId: " + displayId
+ + ", existing displayId: " + mCallbackDisplayId);
}
mDefaultDisplayRotationChangedCallback = callback;
+ mCallbackDisplayId = displayId;
if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
callback.run();
@@ -82,10 +87,17 @@ class DisplayRotationCoordinator {
/**
* Removes the callback that was added via
- * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+ * {@link #setDefaultDisplayRotationChangedCallback(int, Runnable)}.
*/
- void removeDefaultDisplayRotationChangedCallback() {
+ void removeDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+ if (callback != mDefaultDisplayRotationChangedCallback) {
+ Slog.w(TAG, "Attempted to remove non-matching callback."
+ + " DisplayId: " + mCallbackDisplayId);
+ return;
+ }
+
mDefaultDisplayRotationChangedCallback = null;
+ mCallbackDisplayId = Display.INVALID_DISPLAY;
}
static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
index 094434d07cfe..046ed614dc19 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
@@ -17,10 +17,13 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration.Orientation;
@@ -66,6 +69,37 @@ final class DisplayRotationImmersiveAppCompatPolicy {
}
/**
+ * Returns {@code true} if the orientation update should be skipped and it will update when
+ * transition is done. This is to keep the orientation which was preserved by
+ * {@link #isRotationLockEnforced} from being changed by a transient launch (i.e. recents).
+ */
+ boolean deferOrientationUpdate() {
+ if (mDisplayRotation.getUserRotation() != USER_ROTATION_FREE
+ || mDisplayRotation.getLastOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) {
+ return false;
+ }
+ final WindowOrientationListener orientationListener =
+ mDisplayRotation.getOrientationListener();
+ if (orientationListener == null
+ || orientationListener.getProposedRotation() == mDisplayRotation.getRotation()) {
+ return false;
+ }
+ // The above conditions mean that isRotationLockEnforced might have taken effect:
+ // Auto-rotation is enabled and the proposed rotation is not applied.
+ // Then the update should defer until the transition idle to avoid disturbing animation.
+ if (!mDisplayContent.mTransitionController.hasTransientLaunch(mDisplayContent)) {
+ return false;
+ }
+ mDisplayContent.mTransitionController.mStateValidators.add(() -> {
+ if (!isRotationLockEnforcedLocked(orientationListener.getProposedRotation())) {
+ mDisplayContent.mWmService.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ }
+ });
+ return true;
+ }
+
+ /**
* Decides whether it is necessary to lock screen rotation, preventing auto rotation, based on
* the top activity configuration and proposed screen rotation.
*
diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
index b955738e37b2..930d4b186cc1 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
@@ -19,7 +19,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_LOCKED;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index e585efa8a3cc..f6d05d08cb04 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -143,7 +143,7 @@ class DisplayWindowSettings {
}
// No record is present so use default windowing mode policy.
final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
- && (mService.mIsPc || dc.forceDesktopMode());
+ && (mService.mIsPc || dc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
if (forceFreeForm) {
return WindowConfiguration.WINDOWING_MODE_FREEFORM;
}
@@ -275,6 +275,24 @@ class DisplayWindowSettings {
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
+ boolean isIgnoreActivitySizeRestrictionsLocked(@NonNull DisplayContent dc) {
+ final DisplayInfo displayInfo = dc.getDisplayInfo();
+ final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
+ return settings.mIgnoreActivitySizeRestrictions != null
+ && settings.mIgnoreActivitySizeRestrictions;
+ }
+
+ void setIgnoreActivitySizeRestrictionsOnDisplayLocked(@NonNull String displayUniqueId,
+ int displayType, boolean enabled) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = displayUniqueId;
+ displayInfo.type = displayType;
+ final SettingsProvider.SettingsEntry overrideSettings =
+ mSettingsProvider.getOverrideSettings(displayInfo);
+ overrideSettings.mIgnoreActivitySizeRestrictions = enabled;
+ mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+ }
+
void clearDisplaySettings(@NonNull String displayUniqueId, int displayType) {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = displayUniqueId;
@@ -474,6 +492,8 @@ class DisplayWindowSettings {
Boolean mIgnoreDisplayCutout;
@Nullable
Boolean mDontMoveToTop;
+ @Nullable
+ Boolean mIgnoreActivitySizeRestrictions;
SettingsEntry() {}
@@ -557,6 +577,11 @@ class DisplayWindowSettings {
mDontMoveToTop = other.mDontMoveToTop;
changed = true;
}
+ if (!Objects.equals(other.mIgnoreActivitySizeRestrictions,
+ mIgnoreActivitySizeRestrictions)) {
+ mIgnoreActivitySizeRestrictions = other.mIgnoreActivitySizeRestrictions;
+ changed = true;
+ }
return changed;
}
@@ -649,6 +674,11 @@ class DisplayWindowSettings {
mDontMoveToTop = delta.mDontMoveToTop;
changed = true;
}
+ if (delta.mIgnoreActivitySizeRestrictions != null && !Objects.equals(
+ delta.mIgnoreActivitySizeRestrictions, mIgnoreActivitySizeRestrictions)) {
+ mIgnoreActivitySizeRestrictions = delta.mIgnoreActivitySizeRestrictions;
+ changed = true;
+ }
return changed;
}
@@ -667,7 +697,8 @@ class DisplayWindowSettings {
&& mFixedToUserRotation == null
&& mIgnoreOrientationRequest == null
&& mIgnoreDisplayCutout == null
- && mDontMoveToTop == null;
+ && mDontMoveToTop == null
+ && mIgnoreActivitySizeRestrictions == null;
}
@Override
@@ -691,7 +722,9 @@ class DisplayWindowSettings {
&& Objects.equals(mFixedToUserRotation, that.mFixedToUserRotation)
&& Objects.equals(mIgnoreOrientationRequest, that.mIgnoreOrientationRequest)
&& Objects.equals(mIgnoreDisplayCutout, that.mIgnoreDisplayCutout)
- && Objects.equals(mDontMoveToTop, that.mDontMoveToTop);
+ && Objects.equals(mDontMoveToTop, that.mDontMoveToTop)
+ && Objects.equals(mIgnoreActivitySizeRestrictions,
+ that.mIgnoreActivitySizeRestrictions);
}
@Override
@@ -700,7 +733,7 @@ class DisplayWindowSettings {
mForcedHeight, mForcedDensity, mForcedScalingMode, mRemoveContentMode,
mShouldShowWithInsecureKeyguard, mShouldShowSystemDecors, mIsHomeSupported,
mImePolicy, mFixedToUserRotation, mIgnoreOrientationRequest,
- mIgnoreDisplayCutout, mDontMoveToTop);
+ mIgnoreDisplayCutout, mDontMoveToTop, mIgnoreActivitySizeRestrictions);
}
@Override
@@ -722,6 +755,7 @@ class DisplayWindowSettings {
+ ", mIgnoreOrientationRequest=" + mIgnoreOrientationRequest
+ ", mIgnoreDisplayCutout=" + mIgnoreDisplayCutout
+ ", mDontMoveToTop=" + mDontMoveToTop
+ + ", mForceAppsUniversalResizable=" + mIgnoreActivitySizeRestrictions
+ '}';
}
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index c849a37ede53..258a87eae196 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -156,7 +156,7 @@ class DragDropController {
SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId,
float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
+ Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=0x" +
Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
+ touchY + ") thumb center(" + thumbCenterX + "," + thumbCenterY + ")");
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index b09d63fea97f..1c4e487d2e7e 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -24,8 +24,8 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
import static com.android.server.wm.DragDropController.MSG_REMOVE_DRAG_SURFACE_TIMEOUT;
@@ -528,7 +528,7 @@ class DragState {
}
// Only allow the extras to be dispatched to a global-intercepting drag target
ClipData data = null;
- if (interceptsGlobalDrag) {
+ if (interceptsGlobalDrag && mData != null) {
data = mData.copyForTransferWithActivityInfo();
PersistableBundle extras = data.getDescription().getExtras() != null
? data.getDescription().getExtras()
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index e007b1d07b34..907d0dc2e183 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -17,7 +17,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_EMBEDDED_WINDOWS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_EMBEDDED_WINDOWS;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -39,6 +39,8 @@ import android.window.InputTransferToken;
import com.android.internal.protolog.ProtoLog;
import com.android.server.input.InputManagerService;
+import java.util.ArrayList;
+
/**
* Keeps track of embedded windows.
*
@@ -146,6 +148,20 @@ class EmbeddedWindowController {
return mWindowsByWindowToken.get(windowToken);
}
+ @Nullable ArrayList<EmbeddedWindow> getByHostWindow(WindowState host) {
+ ArrayList<EmbeddedWindow> windows = null;
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ final EmbeddedWindow ew = mWindows.valueAt(i);
+ if (ew.mHostWindowState == host) {
+ if (windows == null) {
+ windows = new ArrayList<>();
+ }
+ windows.add(ew);
+ }
+ }
+ return windows;
+ }
+
private boolean isValidTouchGestureParams(WindowState hostWindowState,
EmbeddedWindow embeddedWindow) {
if (embeddedWindow == null) {
@@ -191,8 +207,12 @@ class EmbeddedWindowController {
throw new SecurityException(
"Transfer request must originate from owner of transferFromToken");
}
- return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
- transferToHostWindowState.mInputChannelToken);
+ final boolean didTransfer = mInputManagerService.transferTouchGesture(
+ ew.getInputChannelToken(), transferToHostWindowState.mInputChannelToken);
+ if (didTransfer) {
+ ew.mGestureToEmbedded = false;
+ }
+ return didTransfer;
}
boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
@@ -205,8 +225,15 @@ class EmbeddedWindowController {
throw new SecurityException(
"Transfer request must originate from owner of transferFromToken");
}
- return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
+ final boolean didTransfer = mInputManagerService.transferTouchGesture(
+ hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
+ if (didTransfer) {
+ ew.mGestureToEmbedded = true;
+ mAtmService.mBackNavigationController.onEmbeddedWindowGestureTransferred(
+ hostWindowState);
+ }
+ return didTransfer;
}
static class EmbeddedWindow implements InputTarget {
@@ -235,6 +262,9 @@ class EmbeddedWindowController {
// the host window.
private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0;
+ /** Whether the gesture is transferred to embedded window. */
+ boolean mGestureToEmbedded = false;
+
/**
* @param session calling session to check ownership of the window
* @param clientToken client token used to clean up the map if the embedding process dies
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index cc2249de010c..9d6688648021 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -87,3 +87,16 @@ option java_package com.android.server.wm
# Entering pip called
38000 wm_enter_pip (User|1|5),(Token|1|5),(Component Name|3),(is Auto Enter|3)
+
+# Dim layer is created
+38200 wm_dim_created (Host|3),(Surface|1)
+# Dimmer is ready for removal
+38201 wm_dim_exit (Surface|1),(dimmingWindow|3),(hostIsVisible|1),(removeImmediately|1)
+# Dimmer is starting an animation
+38202 wm_dim_animate (Surface|1, (toAlpha|5), (toBlur|5))
+# Dimmer animation is cancelled
+38203 wm_dim_cancel_anim (Surface|1),(reason|3)
+# Dimmer animation is finished
+38204 wm_dim_finish_anim (Surface|1)
+# Dimmer removing surface
+38205 wm_dim_removed (Surface|1) \ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 8763c8f18f70..98ed6f76b2f9 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -19,7 +19,7 @@ package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsSource.ID_IME;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
@@ -40,6 +40,7 @@ import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.protolog.ProtoLog;
import java.io.PrintWriter;
@@ -100,22 +101,23 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// isLeashReadyForDispatching (used to dispatch the leash of the control) is
// depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
// again, so that the control with leash can be eventually dispatched
- if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending
+ if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending
&& mControlTarget != null) {
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStateController.notifyControlChanged(mControlTarget, this);
setImeShowing(true);
- } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+ } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
&& givenInsetsPending) {
// If the server visibility didn't change (still visible), and mGivenInsetsReady
// is set, we won't call into notifyControlChanged. Therefore, we can reset the
// statsToken, if available.
+ ProtoLog.d(WM_DEBUG_IME, "onPostLayout cancel statsToken, ws=%s", ws);
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
- } else if (wasServerVisible && !mServerVisible) {
+ } else if (wasServerVisible && !isServerVisible()) {
setImeShowing(false);
}
}
@@ -133,15 +135,19 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
}
@Override
- protected boolean isLeashReadyForDispatching(InsetsControlTarget target) {
+ protected boolean isLeashReadyForDispatching() {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // We should only dispatch the leash, if the following conditions are fulfilled:
+ // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+ // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+ // serverVisible (the unfrozen state)
final WindowState ws =
mWindowContainer != null ? mWindowContainer.asWindowState() : null;
final boolean isDrawn = ws != null && ws.isDrawn();
- return super.isLeashReadyForDispatching(target)
- && mServerVisible && isDrawn && mGivenInsetsReady;
+ return super.isLeashReadyForDispatching()
+ && isServerVisible() && isDrawn && mGivenInsetsReady;
} else {
- return super.isLeashReadyForDispatching(target);
+ return super.isLeashReadyForDispatching();
}
}
@@ -171,9 +177,13 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
if (control != null && control.getLeash() != null) {
ImeTracker.Token statsToken = getAndClearStatsToken();
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
- control.setImeStatsToken(statsToken);
+ if (statsToken == null) {
+ ProtoLog.d(WM_DEBUG_IME, "IME getControl without statsToken");
+ } else {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
+ control.setImeStatsToken(statsToken);
+ }
}
}
return control;
@@ -255,7 +265,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
- // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+ // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently
// not the case)
super.updateControlForTarget(target, force, statsToken);
if (Flags.refactorInsetsController()) {
@@ -275,36 +285,14 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (caller != controlTarget) {
if (Flags.refactorInsetsController()) {
if (isImeInputTarget(caller)) {
- // In case of the multi window mode, update the requestedVisibleTypes from
- // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController.
- // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with
- // its new requested visibility for the IME
- boolean imeVisible = caller.isRequestedVisible(WindowInsets.Type.ime());
- if (controlTarget != null) {
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
- controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
- } else if (caller instanceof InsetsControlTarget) {
- // In case of a virtual display that cannot show the IME, the
- // controlTarget will be null here, as no controlTarget was set yet. In
- // that case, proceed similar to the multi window mode (fallback =
- // RemoteInsetsControlTarget of the default display)
- controlTarget = mDisplayContent.getImeHostOrFallback(
- ((InsetsControlTarget) caller).getWindow());
-
- if (controlTarget != caller) {
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
- controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
- } else {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
- }
- }
-
- invokeOnImeRequestedChangedListener(caller, statsToken);
+ reportImeInputTargetStateToControlTarget(caller, controlTarget, statsToken);
} else {
- // TODO(b/353463205) add ImeTracker?
+ ProtoLog.w(WM_DEBUG_IME,
+ "Tried to update client visibility for non-IME input target %s "
+ + "(current target: %s)",
+ caller, mDisplayContent.getImeInputTarget());
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
}
}
return false;
@@ -318,12 +306,14 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
if (Flags.refactorInsetsController()) {
if (changed) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
statsToken);
} else {
- // TODO(b/329229469) change phase and check cancelled / failed
+ // TODO(b/353463205) check cancelled / failed
ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
}
}
return changed;
@@ -333,12 +323,50 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (Flags.refactorInsetsController() && target != null) {
InsetsControlTarget imeControlTarget = getControlTarget();
if (target != imeControlTarget) {
- // If the targetWin is not the imeControlTarget (=RemoteInsetsControlTarget) let it
- // know about the new requestedVisibleTypes for the IME.
- if (imeControlTarget != null) {
- imeControlTarget.setImeInputTargetRequestedVisibility(
- (target.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
- }
+ // TODO(b/353463205): check if fromUser=false is correct here
+ boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime());
+ ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(
+ imeVisible ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_SERVER,
+ imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED
+ : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
+ false /* fromUser */);
+ reportImeInputTargetStateToControlTarget(target, imeControlTarget,
+ statsToken);
+ }
+ }
+ }
+
+ private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget,
+ InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken) {
+ // In case of the multi window mode, update the requestedVisibleTypes from
+ // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController.
+ // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with
+ // its new requested visibility for the IME
+ boolean imeVisible = imeInsetsTarget.isRequestedVisible(WindowInsets.Type.ime());
+ if (controlTarget != null) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
+ controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
+ } else if (imeInsetsTarget instanceof InsetsControlTarget) {
+ // In case of a virtual display that cannot show the IME, the
+ // controlTarget will be null here, as no controlTarget was set yet. In
+ // that case, proceed similar to the multi window mode (fallback =
+ // RemoteInsetsControlTarget of the default display)
+ controlTarget = mDisplayContent.getImeHostOrFallback(
+ ((InsetsControlTarget) imeInsetsTarget).getWindow());
+
+ if (controlTarget != imeInsetsTarget) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
+ controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
+ // not all virtual displays have an ImeInsetsSourceProvider, so it is not
+ // guaranteed that the IME will be started when the control target reports its
+ // requested visibility back. Thus, invoking the listener here.
+ invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
+ } else {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
}
}
}
@@ -376,9 +404,9 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
WindowToken imeToken = mWindowContainer.asWindowState() != null
? mWindowContainer.asWindowState().mToken : null;
final var rotationController = mDisplayContent.getAsyncRotationController();
- if ((rotationController != null && rotationController.isTargetToken(imeToken))
- || (imeToken != null && imeToken.isSelfAnimating(
- 0 /* flags */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) {
+ if ((rotationController != null && rotationController.isTargetToken(imeToken)) || (
+ imeToken != null && imeToken.isSelfAnimating(0 /* flags */,
+ SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) {
// Skip reporting IME drawn state when the control target is in fixed
// rotation, AsyncRotationController will report after the animation finished.
return;
@@ -460,7 +488,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// This can later become ready, so we don't want to cancel the pending request here.
return;
}
- // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+ // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME
// (DefaultImeVisibilityApplier)
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
@@ -637,7 +665,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
sb.append(", leash is: ").append(hasLeash ? "non-null" : "null");
if (!hasLeash) {
sb.append(", control is: ").append(mControl != null ? "non-null" : "null");
- sb.append(", mIsLeashReadyForDispatching: ").append(mIsLeashReadyForDispatching);
+ sb.append(", mIsLeashInitialized: ").append(mIsLeashInitialized);
}
sb.append(", isImeLayeringTarget: ");
sb.append(isImeLayeringTarget(mImeRequester, dcTarget));
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 232c3b62bcef..9d21183c6c03 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -28,9 +29,12 @@ import android.annotation.Nullable;
import android.gui.StalledTransactionInfo;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Trace;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.InputApplicationHandle;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -64,6 +68,12 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
// which point the ActivityManager will enable dispatching.
private boolean mInputDispatchEnabled;
+ /**
+ * The last input devices info which may affect display configuration. This is a quick lookup
+ * to detect interested changes without entering WM lock.
+ */
+ private SparseIntArray mLastInputConfigurationSources;
+
public InputManagerCallback(WindowManagerService service) {
mService = service;
}
@@ -117,8 +127,18 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
/** Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
- synchronized (mService.mGlobalLock) {
- mService.mRoot.forAllDisplays(DisplayContent::sendNewConfiguration);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "notifyConfigurationChanged");
+ final boolean changed = !com.android.window.flags.Flags.filterIrrelevantInputDeviceChange()
+ || updateLastInputConfigurationSources();
+
+ // Even if the input devices are not changed, there could be other pending changes
+ // during booting. It's fine to apply earlier.
+ if (changed || !mService.mDisplayEnabled) {
+ synchronized (mService.mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "inputDeviceConfigChanged");
+ mService.mRoot.forAllDisplays(DisplayContent::sendNewConfiguration);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
synchronized (mInputDevicesReadyMonitor) {
@@ -127,6 +147,40 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
mInputDevicesReadyMonitor.notifyAll();
}
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ /** Returns {@code true} if the change of input devices may affect display configuration. */
+ private boolean updateLastInputConfigurationSources() {
+ final InputDevice[] devices = mService.mInputManager.getInputDevices();
+ final SparseIntArray newSources = new SparseIntArray(8);
+ final SparseIntArray lastSources = mLastInputConfigurationSources;
+ boolean changed = lastSources == null;
+ for (InputDevice device : devices) {
+ final String descriptor = device.getDescriptor();
+ if (descriptor == null || device.isVirtual()) {
+ continue;
+ }
+ final int key = descriptor.hashCode();
+ // The interested attributes from DisplayContent#computeScreenConfiguration.
+ int newSourceHash = device.getSources();
+ newSourceHash = newSourceHash * 31 + device.getKeyboardType();
+ newSourceHash = newSourceHash * 31 + device.getAssociatedDisplayId();
+ newSourceHash = newSourceHash * 31 + (device.isExternal() ? 1 : 0);
+ newSourceHash = newSourceHash * 31 + (device.isEnabled() ? 1 : 0);
+ newSources.put(key, newSourceHash);
+ if (lastSources != null && !changed) {
+ final int lastSource = lastSources.get(key, 0 /* valueIfKeyNotFound */);
+ if (lastSource != newSourceHash) {
+ changed = true;
+ }
+ }
+ }
+ if (lastSources != null && lastSources.size() != newSources.size()) {
+ changed = true;
+ }
+ mLastInputConfigurationSources = newSources;
+ return changed;
}
/** Notifies that the pointer location configuration has changed. */
@@ -188,9 +242,8 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
* the application did not handle.
*/
@Override
- public KeyEvent dispatchUnhandledKey(
- IBinder focusedToken, KeyEvent event, int policyFlags) {
- return mService.mPolicy.dispatchUnhandledKey(focusedToken, event, policyFlags);
+ public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
+ return mService.mPolicy.interceptUnhandledKey(event, focusedToken);
}
/** Callback to get pointer layer. */
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index ddbfd70ea4c4..16e88a3c01ea 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -41,7 +41,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
@@ -222,7 +222,8 @@ final class InputMonitor {
UserHandle clientUser) {
final InputConsumerImpl existingConsumer = getInputConsumer(name);
if (existingConsumer != null && existingConsumer.mClientUser.equals(clientUser)) {
- throw new IllegalStateException("Existing input consumer found with name: " + name
+ destroyInputConsumer(existingConsumer.mToken);
+ Slog.w(TAG_WM, "Replacing existing input consumer found with name: " + name
+ ", display: " + mDisplayId + ", user: " + clientUser);
}
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 7043aacfc44d..cee49676eeae 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
@@ -90,8 +91,10 @@ interface InsetsControlTarget extends InsetsTarget {
/**
* @param visible the requested visibility for the IME, used for
* {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}
+ * @param statsToken the token tracking the current IME request
*/
- default void setImeInputTargetRequestedVisibility(boolean visible) {
+ default void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
}
/** Returns {@code target.getWindow()}, or null if {@code target} is {@code null}. */
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b414a862f874..24a6f118ad04 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -47,6 +47,7 @@ import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
@@ -412,6 +413,22 @@ class InsetsPolicy {
state.addSource(imeSource);
return state;
}
+ } else if (Flags.refactorInsetsController()
+ && (w.mMergedExcludeInsetsTypes & WindowInsets.Type.ime()) != 0) {
+ // In some cases (e.g. split screen from when the IME was requested and the animation
+ // actually starts) the insets should not be send, unless the flag is unset.
+ final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+ if (originalImeSource != null && originalImeSource.isVisible()) {
+ final InsetsState state = copyState
+ ? new InsetsState(originalState)
+ : originalState;
+ final InsetsSource imeSource = new InsetsSource(originalImeSource);
+ // Setting the height to zero, pretending we're in floating mode
+ imeSource.setFrame(0, 0, 0, 0);
+ imeSource.setVisibleFrame(imeSource.getFrame());
+ state.addSource(imeSource);
+ return state;
+ }
}
return originalState;
}
@@ -611,8 +628,9 @@ class InsetsPolicy {
return (mForcedShowingTypes & types) == types;
}
- void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) {
- mForcedShowingTypes = (inSplitScreenMode || inFreeformMode)
+ void updateSystemBars(WindowState win, boolean inSplitScreenMode,
+ boolean inNonFullscreenFreeformMode) {
+ mForcedShowingTypes = (inSplitScreenMode || inNonFullscreenFreeformMode)
? (Type.statusBars() | Type.navigationBars())
: forceShowingNavigationBars(win)
? Type.navigationBars()
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 8d7447c5182f..d1585d06ae40 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.CONTROL;
@@ -71,7 +71,7 @@ class InsetsSourceProvider {
protected @Nullable WindowContainer mWindowContainer;
protected @Nullable InsetsSourceControl mControl;
protected @Nullable InsetsControlTarget mControlTarget;
- protected boolean mIsLeashReadyForDispatching;
+ protected boolean mIsLeashInitialized;
private final Rect mTmpRect = new Rect();
private final InsetsSourceControl mFakeControl;
@@ -162,6 +162,12 @@ class InsetsSourceProvider {
return mSource;
}
+ @VisibleForTesting
+ @NonNull
+ Rect getSourceFrame() {
+ return mSourceFrame;
+ }
+
/**
* @return Whether the current flag configuration allows to control this source.
*/
@@ -378,16 +384,19 @@ class InsetsSourceProvider {
}
final boolean serverVisibleChanged = mServerVisible != isServerVisible;
setServerVisible(isServerVisible);
- final boolean positionChanged = updateInsetsControlPosition(windowState);
- if (mControl != null && mControlTarget != null && !positionChanged
- // The insets hint would be updated if the position is changed. Here updates it for
- // the possible change of the bounds or the server visibility.
- && (updateInsetsHint()
- || serverVisibleChanged
- && android.view.inputmethod.Flags.refactorInsetsController())) {
- // Only call notifyControlChanged here when the position is not changed. Otherwise, it
- // is called or is scheduled to be called during updateInsetsControlPosition.
- mStateController.notifyControlChanged(mControlTarget, this);
+ if (mControl != null && mControlTarget != null) {
+ final boolean positionChanged = updateInsetsControlPosition(windowState);
+ if (!(positionChanged || mHasPendingPosition)
+ // The insets hint would be updated while changing the position. Here updates it
+ // for the possible change of the bounds or the server visibility.
+ && (updateInsetsHint()
+ || (android.view.inputmethod.Flags.refactorInsetsController()))
+ && serverVisibleChanged) {
+ // Only call notifyControlChanged here when the position hasn't been or won't be
+ // changed. Otherwise, it has been called or scheduled to be called during
+ // updateInsetsControlPosition.
+ mStateController.notifyControlChanged(mControlTarget, this);
+ }
}
}
@@ -403,6 +412,7 @@ class InsetsSourceProvider {
mPosition.set(position);
if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
&& windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
+ mHasPendingPosition = true;
windowState.applyWithNextDraw(mSetControlPositionConsumer);
} else {
Transaction t = mWindowContainer.getSyncTransaction();
@@ -566,8 +576,8 @@ class InsetsSourceProvider {
ANIMATION_TYPE_INSETS_CONTROL);
// The leash was just created. We cannot dispatch it until its surface transaction is
- // applied. Otherwise, the client's operation to the leash might be overwritten by us.
- mIsLeashReadyForDispatching = false;
+ // committed. Otherwise, the client's operation to the leash might be overwritten by us.
+ mIsLeashInitialized = false;
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
@@ -603,7 +613,7 @@ class InsetsSourceProvider {
* @param id Indicates which transaction is committed so that stale callbacks can be dropped.
*/
void onSurfaceTransactionCommitted(long id) {
- if (mIsLeashReadyForDispatching) {
+ if (mIsLeashInitialized) {
return;
}
if (mControl == null) {
@@ -612,7 +622,7 @@ class InsetsSourceProvider {
if (id != getSurfaceTransactionId(mControl.getLeash())) {
return;
}
- mIsLeashReadyForDispatching = true;
+ mIsLeashInitialized = true;
mStateController.notifySurfaceTransactionReady(this, 0, false);
}
@@ -663,9 +673,12 @@ class InsetsSourceProvider {
mServerVisible, mClientVisible);
}
- protected boolean isLeashReadyForDispatching(InsetsControlTarget target) {
- // If the target is not the control target, we are ready for dispatching a null-leash to it.
- return target != mControlTarget || mIsLeashReadyForDispatching;
+ protected boolean isLeashReadyForDispatching() {
+ return isLeashInitialized();
+ }
+
+ boolean isLeashInitialized() {
+ return mIsLeashInitialized;
}
/**
@@ -678,7 +691,7 @@ class InsetsSourceProvider {
@Nullable
InsetsSourceControl getControl(InsetsControlTarget target) {
if (target == mControlTarget) {
- if (!isLeashReadyForDispatching(target) && mControl != null) {
+ if (!isLeashReadyForDispatching() && mControl != null) {
// The surface transaction of preparing leash is not applied yet. We don't send it
// to the client in case that the client applies its transaction sooner than ours
// that we could unexpectedly overwrite the surface state.
@@ -703,7 +716,7 @@ class InsetsSourceProvider {
*/
@Nullable
protected SurfaceControl getLeash(@NonNull InsetsControlTarget target) {
- return target == mControlTarget && mIsLeashReadyForDispatching && mControl != null
+ return target == mControlTarget && mIsLeashInitialized && mControl != null
? mControl.getLeash() : null;
}
@@ -717,6 +730,10 @@ class InsetsSourceProvider {
return mFakeControlTarget;
}
+ boolean isServerVisible() {
+ return mServerVisible;
+ }
+
boolean isClientVisible() {
return mClientVisible;
}
@@ -752,7 +769,7 @@ class InsetsSourceProvider {
pw.println();
}
pw.print(prefix);
- pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
+ pw.print("mIsLeashInitialized="); pw.print(mIsLeashInitialized);
pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition);
pw.println();
if (mWindowContainer != null) {
@@ -798,7 +815,7 @@ class InsetsSourceProvider {
if (mAdapter != null && mAdapter.mCapturedLeash != null) {
mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH);
}
- proto.write(IS_LEASH_READY_FOR_DISPATCHING, mIsLeashReadyForDispatching);
+ proto.write(IS_LEASH_READY_FOR_DISPATCHING, isLeashReadyForDispatching());
proto.write(CLIENT_VISIBLE, mClientVisible);
proto.write(SERVER_VISIBLE, mServerVisible);
proto.write(SEAMLESS_ROTATING, mSeamlessRotating);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 6ae23410864e..cd3398950099 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -24,7 +24,7 @@ import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.systemGestures;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
@@ -228,13 +228,11 @@ class InsetsStateController {
changed |= provider.updateClientVisibility(caller,
isImeProvider ? statsToken : null);
}
- if (!android.view.inputmethod.Flags.refactorInsetsController()) {
- if (changed) {
- notifyInsetsChanged();
- mDisplayContent.updateSystemGestureExclusion();
+ if (changed) {
+ notifyInsetsChanged();
+ mDisplayContent.updateSystemGestureExclusion();
- mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
- }
+ mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
}
}
@@ -317,9 +315,9 @@ class InsetsStateController {
// aborted.
provider.updateFakeControlTarget(target);
} else {
- // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+ // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail
provider.updateControlForTarget(target, false /* force */,
- null /* TODO(b/329229469) check if needed here */);
+ null /* TODO(b/353463205) check if needed here */);
// Get control target again in case the provider didn't accept the one we passed to it.
target = provider.getControlTarget();
@@ -420,7 +418,7 @@ class InsetsStateController {
final ArrayList<InsetsSourceProvider> providers = pendingControlMap.valueAt(i);
for (int p = providers.size() - 1; p >= 0; p--) {
final InsetsSourceProvider provider = providers.get(p);
- if (provider.isLeashReadyForDispatching(target)) {
+ if (provider.isLeashInitialized() || provider.getControlTarget() != target) {
// Stop waiting for this provider.
providers.remove(p);
}
@@ -458,6 +456,12 @@ class InsetsStateController {
mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
}
+ void notifyInsetsChanged(ArraySet<WindowState> changedWindows) {
+ for (int i = changedWindows.size() - 1; i >= 0; i--) {
+ mDispatchInsetsChanged.accept(changedWindows.valueAt(i));
+ }
+ }
+
/**
* Checks if the control target has pending controls.
*
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 0c489d6207e9..6091b8334438 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -62,6 +62,7 @@ import android.view.WindowManager;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -73,6 +74,9 @@ import java.io.PrintWriter;
*/
class KeyguardController {
+ private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+ Flags.ensureKeyguardDoesTransitionStarting();
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardController" : TAG_ATM;
static final String KEYGUARD_SLEEP_TOKEN_TAG = "keyguard";
@@ -201,6 +205,19 @@ class KeyguardController {
setWakeTransitionReady();
return;
}
+
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ final TransitionController transitionController =
+ mWindowManager.mAtmService.getTransitionController();
+ final Transition transition = transitionController.getCollectingTransition();
+ if (transition != null && displayId == DEFAULT_DISPLAY) {
+ if (!keyguardShowing && state.mKeyguardShowing) {
+ transition.addFlag(TRANSIT_FLAG_KEYGUARD_GOING_AWAY);
+ } else if (keyguardShowing && !state.mKeyguardShowing) {
+ transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING);
+ }
+ }
+ }
// Update the task snapshot if the screen will not be turned off. To make sure that the
// unlocking animation can animate consistent content. The conditions are:
// - Either AOD or keyguard changes to be showing. So if the states change individually,
@@ -231,8 +248,10 @@ class KeyguardController {
|| (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
// Keyguard decided to show or stopped going away. Send a transition to animate back
// to the locked state before holding the sleep token again
- dc.requestTransitionAndLegacyPrepare(
- TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+ if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ dc.requestTransitionAndLegacyPrepare(
+ TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+ }
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
}
@@ -310,7 +329,7 @@ class KeyguardController {
// If the client has requested to dismiss the keyguard and the Activity has the flag to
// turn the screen on, wakeup the screen if it's the top Activity.
if (activityRecord.getTurnScreenOnFlag() && activityRecord.isTopRunningActivity()) {
- mTaskSupervisor.wakeUp("dismissKeyguard");
+ mTaskSupervisor.wakeUp(activityRecord.getDisplayId(), "dismissKeyguard");
}
mWindowManager.dismissKeyguard(callback, message);
@@ -746,26 +765,27 @@ class KeyguardController {
}
if (mTopTurnScreenOnActivity != null
- && !mService.mWindowManager.mPowerManager.isInteractive()
+ && !mService.mWindowManager.mPowerManager.isInteractive(display.getDisplayId())
&& (mRequestDismissKeyguard || mOccluded)) {
- controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
+ controller.mTaskSupervisor.wakeUp(display.getDisplayId(), "handleTurnScreenOn");
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
- boolean hasChange = false;
- if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+ final boolean startedGoingAway = (!lastKeyguardGoingAway && mKeyguardGoingAway);
+ final boolean occludedChanged = (lastOccluded != mOccluded);
+
+ if (startedGoingAway) {
writeEventLog("dismissIfInsecure");
controller.handleDismissInsecureKeyguard(display);
controller.scheduleGoingAwayTimeout(mDisplayId);
- hasChange = true;
- } else if (lastOccluded != mOccluded) {
+ }
+ if (occludedChanged && (reduceKeyguardTransitions() || !startedGoingAway)) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
- hasChange = true;
}
// Collect the participants for shell transition, so that transition won't happen too
// early since the transition was set ready.
- if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+ if (top != null && (startedGoingAway || (occludedChanged && mOccluded))) {
display.mTransitionController.collect(top);
}
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index 2394da91684d..4aa4f22ec148 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -22,6 +22,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManagerInternal;
import android.graphics.Rect;
import android.os.Environment;
+import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -50,6 +51,9 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
import java.util.function.IntFunction;
/**
@@ -84,6 +88,12 @@ class LaunchParamsPersister {
private PackageList mPackageList;
/**
+ * A map from user ID to the active {@link LoadingTask} when we're loading the launch params for
+ * that user.
+ */
+ private final SparseArray<LoadingTask> mLoadingTaskMap = new SparseArray<>();
+
+ /**
* A dual layer map that first maps user ID to a secondary map, which maps component name (the
* launching activity of tasks) to {@link PersistableLaunchParams} that stores launch metadata
* that are stable across reboots.
@@ -117,113 +127,33 @@ class LaunchParamsPersister {
}
void onUnlockUser(int userId) {
- loadLaunchParams(userId);
+ if (mLoadingTaskMap.contains(userId)) {
+ Slog.e(TAG, "Duplicated onUnlockUser " + userId);
+ return;
+ }
+
+ final LoadingTask task = new LoadingTask(userId);
+ mLoadingTaskMap.put(userId, task);
+ task.execute();
}
void onCleanupUser(int userId) {
+ // There is no need to abort the task itself. Just let the loading task finish silently
+ // without modifying any state.
+ mLoadingTaskMap.remove(userId);
mLaunchParamsMap.remove(userId);
}
- private void loadLaunchParams(int userId) {
- final List<File> filesToDelete = new ArrayList<>();
- final File launchParamsFolder = getLaunchParamFolder(userId);
- if (!launchParamsFolder.isDirectory()) {
- Slog.i(TAG, "Didn't find launch param folder for user " + userId);
+ private void waitAndMoveResultIfLoading(int userId) {
+ final LoadingTask task = mLoadingTaskMap.removeReturnOld(userId);
+ if (task == null) {
return;
}
-
- final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());
-
- final File[] paramsFiles = launchParamsFolder.listFiles();
- final ArrayMap<ComponentName, PersistableLaunchParams> map =
- new ArrayMap<>(paramsFiles.length);
- mLaunchParamsMap.put(userId, map);
-
- for (File paramsFile : paramsFiles) {
- if (!paramsFile.isFile()) {
- Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
- continue;
- }
- if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
- Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
- filesToDelete.add(paramsFile);
- continue;
- }
- String paramsFileName = paramsFile.getName();
- // Migrate all records from old separator to new separator.
- final int oldSeparatorIndex =
- paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR);
- if (oldSeparatorIndex != -1) {
- if (paramsFileName.indexOf(
- OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) {
- // Rare case. We have more than one old escaped component separator probably
- // because this app uses underscore in their package name. We can't distinguish
- // which one is the real separator so let's skip it.
- filesToDelete.add(paramsFile);
- continue;
- }
- paramsFileName = paramsFileName.replace(
- OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR);
- final File newFile = new File(launchParamsFolder, paramsFileName);
- if (paramsFile.renameTo(newFile)) {
- paramsFile = newFile;
- } else {
- // Rare case. For some reason we can't rename the file. Let's drop this record
- // instead.
- filesToDelete.add(paramsFile);
- continue;
- }
- }
- final String componentNameString = paramsFileName.substring(
- 0 /* beginIndex */,
- paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length())
- .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR);
- final ComponentName name = ComponentName.unflattenFromString(
- componentNameString);
- if (name == null) {
- Slog.w(TAG, "Unexpected file name: " + paramsFileName);
- filesToDelete.add(paramsFile);
- continue;
- }
-
- if (!packages.contains(name.getPackageName())) {
- // Rare case. PersisterQueue doesn't have a chance to remove files for removed
- // packages last time.
- filesToDelete.add(paramsFile);
- continue;
- }
-
- try (InputStream in = new FileInputStream(paramsFile)) {
- final PersistableLaunchParams params = new PersistableLaunchParams();
- final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
- && event != XmlPullParser.END_TAG) {
- if (event != XmlPullParser.START_TAG) {
- continue;
- }
-
- final String tagName = parser.getName();
- if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
- Slog.w(TAG, "Unexpected tag name: " + tagName);
- continue;
- }
-
- params.restore(paramsFile, parser);
- }
-
- map.put(name, params);
- addComponentNameToLaunchParamAffinityMapIfNotNull(
- name, params.mWindowLayoutAffinity);
- } catch (Exception e) {
- Slog.w(TAG, "Failed to restore launch params for " + name, e);
- filesToDelete.add(paramsFile);
- }
- }
-
- if (!filesToDelete.isEmpty()) {
- mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
+ final ArrayMap<ComponentName, PersistableLaunchParams> map = task.get();
+ if (map == null) {
+ return;
}
+ mLaunchParamsMap.put(userId, map);
}
void saveTask(Task task) {
@@ -236,6 +166,7 @@ class LaunchParamsPersister {
return;
}
final int userId = task.mUserId;
+ waitAndMoveResultIfLoading(userId);
PersistableLaunchParams params;
ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId);
if (map == null) {
@@ -297,6 +228,7 @@ class LaunchParamsPersister {
void getLaunchParams(Task task, ActivityRecord activity, LaunchParams outParams) {
final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent;
final int userId = task != null ? task.mUserId : activity.mUserId;
+ waitAndMoveResultIfLoading(userId);
final String windowLayoutAffinity;
if (task != null) {
windowLayoutAffinity = task.mWindowLayoutAffinity;
@@ -394,6 +326,137 @@ class LaunchParamsPersister {
}
}
+ private class LoadingTask
+ implements Callable<ArrayMap<ComponentName, PersistableLaunchParams>> {
+ private final int mUserId;
+ private final FutureTask<ArrayMap<ComponentName, PersistableLaunchParams>> mFutureTask;
+
+ private LoadingTask(int userId) {
+ mUserId = userId;
+ mFutureTask = new FutureTask<>(this);
+ }
+
+ private void execute() {
+ new Thread(mFutureTask).start();
+ }
+
+ private ArrayMap<ComponentName, PersistableLaunchParams> get() {
+ try {
+ return mFutureTask.get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ Slog.e(TAG, "Failed to load launch params for user#" + mUserId, e);
+ return null;
+ }
+ }
+
+ @Override
+ public ArrayMap<ComponentName, PersistableLaunchParams> call() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ final List<File> filesToDelete = new ArrayList<>();
+ final File launchParamsFolder = getLaunchParamFolder(mUserId);
+ if (!launchParamsFolder.isDirectory()) {
+ Slog.i(TAG, "Didn't find launch param folder for user " + mUserId);
+ return null;
+ }
+
+ final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());
+
+ final File[] paramsFiles = launchParamsFolder.listFiles();
+ final ArrayMap<ComponentName, PersistableLaunchParams> map =
+ new ArrayMap<>(paramsFiles.length);
+
+ for (File paramsFile : paramsFiles) {
+ if (!paramsFile.isFile()) {
+ Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
+ continue;
+ }
+ if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
+ Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
+ filesToDelete.add(paramsFile);
+ continue;
+ }
+ String paramsFileName = paramsFile.getName();
+ // Migrate all records from old separator to new separator.
+ final int oldSeparatorIndex =
+ paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR);
+ if (oldSeparatorIndex != -1) {
+ if (paramsFileName.indexOf(
+ OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) {
+ // Rare case. We have more than one old escaped component separator probably
+ // because this app uses underscore in their package name. We can't
+ // distinguish which one is the real separator so let's skip it.
+ filesToDelete.add(paramsFile);
+ continue;
+ }
+ paramsFileName = paramsFileName.replace(
+ OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR);
+ final File newFile = new File(launchParamsFolder, paramsFileName);
+ if (paramsFile.renameTo(newFile)) {
+ paramsFile = newFile;
+ } else {
+ // Rare case. For some reason we can't rename the file. Let's drop this
+ // record instead.
+ filesToDelete.add(paramsFile);
+ continue;
+ }
+ }
+ final String componentNameString = paramsFileName.substring(
+ 0 /* beginIndex */,
+ paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length())
+ .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR);
+ final ComponentName name = ComponentName.unflattenFromString(
+ componentNameString);
+ if (name == null) {
+ Slog.w(TAG, "Unexpected file name: " + paramsFileName);
+ filesToDelete.add(paramsFile);
+ continue;
+ }
+
+ if (!packages.contains(name.getPackageName())) {
+ // Rare case. PersisterQueue doesn't have a chance to remove files for removed
+ // packages last time.
+ filesToDelete.add(paramsFile);
+ continue;
+ }
+
+ try (InputStream in = new FileInputStream(paramsFile)) {
+ final PersistableLaunchParams params = new PersistableLaunchParams();
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
+ && event != XmlPullParser.END_TAG) {
+ if (event != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String tagName = parser.getName();
+ if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
+ Slog.w(TAG, "Unexpected tag name: " + tagName);
+ continue;
+ }
+
+ params.restore(paramsFile, parser);
+ }
+
+ map.put(name, params);
+ addComponentNameToLaunchParamAffinityMapIfNotNull(
+ name, params.mWindowLayoutAffinity);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to restore launch params for " + name, e);
+ filesToDelete.add(paramsFile);
+ }
+ }
+
+ if (!filesToDelete.isEmpty()) {
+ mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
+ }
+ return map;
+ }
+ }
+
private class LaunchParamsWriteQueueItem
implements PersisterQueue.WriteQueueItem<LaunchParamsWriteQueueItem> {
private final int mUserId;
@@ -466,7 +529,7 @@ class LaunchParamsPersister {
}
}
- private class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem {
+ private static class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem {
private final List<File> mComponentFiles;
private CleanUpComponentQueueItem(List<File> componentFiles) {
@@ -483,7 +546,7 @@ class LaunchParamsPersister {
}
}
- private class PersistableLaunchParams {
+ private static class PersistableLaunchParams {
private static final String ATTR_WINDOWING_MODE = "windowing_mode";
private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id";
private static final String ATTR_BOUNDS = "bounds";
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 252590e0b696..ca47133a0674 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.SurfaceControl.HIDDEN;
import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND;
+import static android.window.TaskConstants.TASK_CHILD_LAYER_TASK_OVERLAY;
import android.annotation.NonNull;
import android.graphics.Color;
@@ -37,6 +38,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.server.UiThread;
+import com.android.window.flags.Flags;
import java.util.function.Supplier;
@@ -66,6 +68,7 @@ public class Letterbox {
// for overlaping an app window and letterbox surfaces.
private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
+
@NonNull
private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
@NonNull
@@ -222,12 +225,14 @@ public class Letterbox {
void onMovedToDisplay(int displayId) {
for (LetterboxSurface surface : mSurfaces) {
- if (surface.mInputInterceptor != null) {
- surface.mInputInterceptor.mWindowHandle.displayId = displayId;
- }
+ setSurfaceDisplayID(surface, displayId);
}
- if (mFullWindowSurface.mInputInterceptor != null) {
- mFullWindowSurface.mInputInterceptor.mWindowHandle.displayId = displayId;
+ setSurfaceDisplayID(mFullWindowSurface, displayId);
+ }
+
+ private void setSurfaceDisplayID(LetterboxSurface surface, int displayId) {
+ if (surface.mInputInterceptor != null) {
+ surface.mInputInterceptor.mWindowHandle.displayId = displayId;
}
}
@@ -242,14 +247,13 @@ public class Letterbox {
private final class TapEventReceiver extends InputEventReceiver {
private final GestureDetector mDoubleTapDetector;
- private final DoubleTapListener mDoubleTapListener;
TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService,
Handler uiHandler) {
super(inputChannel, uiHandler.getLooper());
- mDoubleTapListener = new DoubleTapListener(wmService);
- mDoubleTapDetector = new GestureDetector(wmService.mContext, mDoubleTapListener,
- uiHandler);
+ final DoubleTapListener doubleTapListener = new DoubleTapListener(wmService);
+ mDoubleTapDetector =
+ new GestureDetector(wmService.mContext, doubleTapListener, uiHandler);
}
@Override
@@ -293,7 +297,8 @@ public class Letterbox {
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
mHandler = UiThread.getHandler();
- final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
+ final String name = namePrefix
+ + (win.mActivityRecord != null ? win.mActivityRecord : win);
mClientChannel = mWmService.mInputManager.createInputChannel(name);
mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService, mHandler);
@@ -303,12 +308,15 @@ public class Letterbox {
win.getDisplayId());
mWindowHandle.name = name;
mWindowHandle.token = mToken;
- mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+ mWindowHandle.layoutParamsType = Flags.scrollingFromLetterbox()
+ ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+ : WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
mWindowHandle.ownerPid = WindowManagerService.MY_PID;
mWindowHandle.ownerUid = WindowManagerService.MY_UID;
mWindowHandle.scaleFactor = 1.0f;
- mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.SLIPPERY;
+ mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE
+ | (Flags.scrollingFromLetterbox() ? InputConfig.SPY : InputConfig.SLIPPERY);
}
void updateTouchableRegion(Rect frame) {
@@ -341,6 +349,7 @@ public class Letterbox {
private final String mType;
private SurfaceControl mSurface;
+ private SurfaceControl mInputSurface;
private Color mColor;
private boolean mHasWallpaperBackground;
private SurfaceControl mParentSurface;
@@ -373,22 +382,36 @@ public class Letterbox {
.setColorSpaceAgnostic(mSurface, true);
}
+ private void createInputSurface(SurfaceControl.Transaction t) {
+ mInputSurface = mSurfaceControlFactory.get()
+ .setName("LetterboxInput - " + mType)
+ .setFlags(HIDDEN)
+ .setContainerLayer()
+ .setOpaque(true)
+ .setCallsite("LetterboxSurface.createInputSurface")
+ .build();
+
+ t.setLayer(mInputSurface, TASK_CHILD_LAYER_TASK_OVERLAY);
+ }
+
void attachInput(WindowState win) {
if (mInputInterceptor != null) {
mInputInterceptor.dispose();
}
+ // TODO(b/371179559): only detect double tap on LB surfaces not used for cutout area.
+ // Potentially, the input interceptor may still be needed for slippery feature.
mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win);
}
- boolean isRemoved() {
- return mSurface != null || mInputInterceptor != null;
- }
-
public void remove() {
if (mSurface != null) {
mTransactionFactory.get().remove(mSurface).apply();
mSurface = null;
}
+ if (mInputSurface != null) {
+ mTransactionFactory.get().remove(mInputSurface).apply();
+ mInputSurface = null;
+ }
if (mInputInterceptor != null) {
mInputInterceptor.dispose();
mInputInterceptor = null;
@@ -415,28 +438,54 @@ public class Letterbox {
createSurface(t);
}
+ if (Flags.scrollingFromLetterbox()
+ && mInputInterceptor != null
+ && mInputSurface == null) {
+ createInputSurface(inputT);
+ }
+
mColor = mAppCompatLetterboxOverrides.getLetterboxBackgroundColor();
mParentSurface = mParentSurfaceSupplier.get();
t.setColor(mSurface, getRgbColorArray());
- t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
- t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
- mSurfaceFrameRelative.height());
- t.reparent(mSurface, mParentSurface);
+ setPositionAndReparent(t, mSurface);
mHasWallpaperBackground = mAppCompatLetterboxOverrides
.hasWallpaperBackgroundForLetterbox();
updateAlphaAndBlur(t);
t.show(mSurface);
- } else if (mSurface != null) {
- t.hide(mSurface);
+
+ if (mInputSurface != null) {
+ setPositionAndReparent(inputT, mInputSurface);
+ inputT.setTrustedOverlay(mInputSurface, true);
+ inputT.show(mInputSurface);
+ }
+
+ } else {
+ if (mSurface != null) {
+ t.hide(mSurface);
+ }
+ if (mInputSurface != null) {
+ inputT.hide(mInputSurface);
+ }
}
- if (mSurface != null && mInputInterceptor != null) {
+
+ SurfaceControl surfaceWithInput =
+ Flags.scrollingFromLetterbox() ? mInputSurface : mSurface;
+ if (surfaceWithInput != null && mInputInterceptor != null) {
mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative);
- inputT.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle);
+ inputT.setInputWindowInfo(surfaceWithInput, mInputInterceptor.mWindowHandle);
}
}
+ private void setPositionAndReparent(@NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl surface) {
+ t.setPosition(surface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
+ t.setWindowCrop(surface, mSurfaceFrameRelative.width(),
+ mSurfaceFrameRelative.height());
+ t.reparent(surface, mParentSurface);
+ }
+
private void updateAlphaAndBlur(SurfaceControl.Transaction t) {
if (!mHasWallpaperBackground) {
// Opaque
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index e65396e00b20..06049530da18 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -32,7 +32,7 @@ import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_CURRENT;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_LOCKTASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
diff --git a/services/core/java/com/android/server/wm/MirrorActiveUids.java b/services/core/java/com/android/server/wm/MirrorActiveUids.java
index b9aa9599babe..b7bf16257f24 100644
--- a/services/core/java/com/android/server/wm/MirrorActiveUids.java
+++ b/services/core/java/com/android/server/wm/MirrorActiveUids.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import android.app.ActivityManager.ProcessState;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import java.io.PrintWriter;
@@ -34,6 +35,8 @@ class MirrorActiveUids {
/** Uid -> number of non-app visible windows belong to the uid. */
private final SparseIntArray mNumNonAppVisibleWindowMap = new SparseIntArray();
+ /** Type -> Uid -> number of non-app visible windows for type/uid. */
+ private final SparseArray<SparseIntArray> mNumNonAppVisibleWindowMapByType = new SparseArray();
synchronized void onUidActive(int uid, int procState) {
mUidStates.put(uid, procState);
@@ -55,17 +58,31 @@ class MirrorActiveUids {
}
/** Called when the surface of non-application (exclude toast) window is shown or hidden. */
- synchronized void onNonAppSurfaceVisibilityChanged(int uid, boolean visible) {
- final int index = mNumNonAppVisibleWindowMap.indexOfKey(uid);
+ synchronized void onNonAppSurfaceVisibilityChanged(int uid, int type, boolean visible) {
+ updateCount(uid, visible, mNumNonAppVisibleWindowMap);
+ updateCount(uid, visible, getNumNonAppVisibleWindowMapByType(type));
+ }
+
+ private SparseIntArray getNumNonAppVisibleWindowMapByType(int type) {
+ SparseIntArray result = mNumNonAppVisibleWindowMapByType.get(type);
+ if (result == null) {
+ result = new SparseIntArray();
+ mNumNonAppVisibleWindowMapByType.append(type, result);
+ }
+ return result;
+ }
+
+ private void updateCount(int uid, boolean visible, SparseIntArray numNonAppVisibleWindowMap) {
+ final int index = numNonAppVisibleWindowMap.indexOfKey(uid);
if (index >= 0) {
- final int num = mNumNonAppVisibleWindowMap.valueAt(index) + (visible ? 1 : -1);
+ final int num = numNonAppVisibleWindowMap.valueAt(index) + (visible ? 1 : -1);
if (num > 0) {
- mNumNonAppVisibleWindowMap.setValueAt(index, num);
+ numNonAppVisibleWindowMap.setValueAt(index, num);
} else {
- mNumNonAppVisibleWindowMap.removeAt(index);
+ numNonAppVisibleWindowMap.removeAt(index);
}
} else if (visible) {
- mNumNonAppVisibleWindowMap.append(uid, 1);
+ numNonAppVisibleWindowMap.append(uid, 1);
}
}
@@ -78,6 +95,24 @@ class MirrorActiveUids {
return mNumNonAppVisibleWindowMap.get(uid) > 0;
}
+ /**
+ * Returns details about the windows that contribute to the result of
+ * {@link #hasNonAppVisibleWindow(int)}.
+ *
+ * @return a map of window type to count
+ */
+ synchronized SparseIntArray getNonAppVisibleWindowDetails(int uid) {
+ SparseIntArray result = new SparseIntArray();
+ for (int i = 0; i < mNumNonAppVisibleWindowMapByType.size(); i++) {
+ SparseIntArray numNonAppVisibleWindowMap = mNumNonAppVisibleWindowMapByType.valueAt(i);
+ int count = numNonAppVisibleWindowMap.get(uid);
+ if (count > 0) {
+ result.append(mNumNonAppVisibleWindowMapByType.keyAt(i), count);
+ }
+ }
+ return result;
+ }
+
synchronized void dump(PrintWriter pw, String prefix) {
pw.print(prefix + "NumNonAppVisibleWindowUidMap:[");
for (int i = mNumNonAppVisibleWindowMap.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 403d3bd5c008..91598c5cc27a 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -22,7 +22,7 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
new file mode 100644
index 000000000000..8c50913dd563
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.text.Html.FROM_HTML_MODE_COMPACT;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.text.Html;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+
+/**
+ * Show warning dialog when
+ * - Uncompressed libs inside apk are not aligned to page size
+ * - ELF Load segments are not page size aligned
+ * This dialog will be shown everytime when app is launched. Apps can choose to override
+ * by setting compat mode pageSizeCompat="enabled" in manifest or "disabled" to opt out.
+ * Both cases will skip the PageSizeMismatchDialog.
+ *
+ */
+class PageSizeMismatchDialog extends AppWarnings.BaseDialog {
+ PageSizeMismatchDialog(
+ final AppWarnings manager,
+ Context context,
+ ApplicationInfo appInfo,
+ int userId,
+ String warning) {
+ super(manager, context, appInfo.packageName, userId);
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label =
+ appInfo.loadSafeLabel(
+ pm,
+ PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
+ PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE
+ | PackageItemInfo.SAFE_LABEL_FLAG_TRIM);
+
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(context)
+ .setPositiveButton(
+ R.string.ok,
+ (dialog, which) -> {/* Do nothing */})
+ .setMessage(Html.fromHtml(warning, FROM_HTML_MODE_COMPACT))
+ .setTitle(label);
+
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9cde39783b7f..44f000da3d73 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -24,6 +24,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -37,8 +39,8 @@ import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
+import static com.android.launcher3.Flags.enableUseTopVisibleActivityForExcludeFromRecentTask;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
@@ -324,9 +326,12 @@ class RecentTasks {
ProtoLog.i(WM_DEBUG_TASKS, "Setting frozen recents task list");
// Always update the reordering time when this is called to ensure that the timeout
- // is reset
+ // is reset. Extend this duration when running in tests.
+ final long timeout = ActivityManager.isRunningInUserTestHarness()
+ ? mFreezeTaskListTimeoutMs * 10
+ : mFreezeTaskListTimeoutMs;
mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
- mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
+ mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
}
/**
@@ -490,11 +495,18 @@ class RecentTasks {
mTaskNotificationController.notifyTaskListUpdated();
}
- private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
+ private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess,
+ boolean removedForAddTask) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);
}
mTaskNotificationController.notifyTaskListUpdated();
+ if (removedForAddTask) {
+ mTaskNotificationController.notifyRecentTaskRemovedForAddTask(task.mTaskId);
+ }
+ }
+ private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
+ notifyTaskRemoved(task, wasTrimmed, killProcess, false /* removedForAddTask */);
}
/**
@@ -549,6 +561,12 @@ class RecentTasks {
long currentElapsedTime = SystemClock.elapsedRealtime();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
+ // Remove the task restored from xml if any existing tasks match.
+ if (findRemoveIndexForAddTask(task) >= 0) {
+ tasks.remove(i);
+ i--;
+ continue;
+ }
task.lastActiveTime = currentElapsedTime - i;
}
@@ -559,6 +577,7 @@ class RecentTasks {
if (existedTaskIds.size() > 0) {
syncPersistentTaskIdsLocked();
}
+ mTaskNotificationController.notifyTaskListUpdated();
}
private boolean isRecentTasksLoaded(int userId) {
@@ -677,27 +696,35 @@ class RecentTasks {
if (isRecentTasksLoaded(userId)) {
Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
mUsersWithRecentsLoaded.delete(userId);
- removeTasksForUserLocked(userId);
+ removeTasksForUserFromMemoryLocked(userId);
}
mPersistedTaskIds.delete(userId);
mTaskPersister.unloadUserDataFromMemory(userId);
}
/** Remove recent tasks for a user. */
- private void removeTasksForUserLocked(int userId) {
+ private void removeTasksForUserFromMemoryLocked(int userId) {
if (userId <= 0) {
Slog.i(TAG, "Can't remove recent task on user " + userId);
return;
}
+ boolean notifyTaskUpdated = false;
for (int i = mTasks.size() - 1; i >= 0; --i) {
Task task = mTasks.get(i);
if (task.mUserId == userId) {
ProtoLog.i(WM_DEBUG_TASKS, "remove RecentTask %s when finishing user "
+ "%d", task, userId);
- remove(task);
+ mTasks.remove(task);
+ mService.mWindowManager.mSnapshotController.mTaskSnapshotController
+ .removeSnapshotCache(task.mTaskId);
+ // Only notify if list has changed.
+ notifyTaskUpdated = true;
}
}
+ if (notifyTaskUpdated) {
+ mTaskNotificationController.notifyTaskListUpdated();
+ }
}
void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
@@ -1502,7 +1529,7 @@ class RecentTasks {
// The Recents is only supported on default display now, we should only keep the
// most recent task of home display.
boolean isMostRecentTask;
- if (enableRefactorTaskThumbnail()) {
+ if (enableUseTopVisibleActivityForExcludeFromRecentTask()) {
isMostRecentTask = task.getTopVisibleActivity() != null;
} else {
isMostRecentTask = taskIndex == 0;
@@ -1615,7 +1642,8 @@ class RecentTasks {
// from becoming dangling.
mHiddenTasks.add(0, removedTask);
}
- notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
+ notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */,
+ true /* removedForAddTask */);
if (DEBUG_RECENTS_TRIM_TASKS) {
Slog.d(TAG, "Trimming task=" + removedTask
+ " for addition of task=" + task);
@@ -2005,22 +2033,9 @@ class RecentTasks {
// Fill in some deprecated values.
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
- rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
if (!getTasksAllowed) {
Task.trimIneffectiveInfo(tr, rti);
}
-
- // Fill in organized child task info for the task created by organizer.
- if (tr.mCreatedByOrganizer) {
- for (int i = tr.getChildCount() - 1; i >= 0; i--) {
- final Task childTask = tr.getChildAt(i).asTask();
- if (childTask != null && childTask.isOrganized()) {
- final ActivityManager.RecentTaskInfo cti = new ActivityManager.RecentTaskInfo();
- childTask.fillTaskInfo(cti, true /* stripExtras */, tda);
- rti.childrenTaskInfos.add(cti);
- }
- }
- }
return rti;
}
@@ -2040,10 +2055,15 @@ class RecentTasks {
final boolean isOtherUndefinedMode = otherWindowingMode == WINDOWING_MODE_UNDEFINED;
// An activity type and windowing mode is compatible if they are the exact same type/mode,
- // or if one of the type/modes is undefined
+ // or if one of the type/modes is undefined. This is with the exception of
+ // freeform/fullscreen where both modes are assumed to be compatible with each other.
final boolean isCompatibleType = activityType == otherActivityType
|| isUndefinedType || isOtherUndefinedType;
final boolean isCompatibleMode = windowingMode == otherWindowingMode
+ || (windowingMode == WINDOWING_MODE_FREEFORM
+ && otherWindowingMode == WINDOWING_MODE_FULLSCREEN)
+ || (windowingMode == WINDOWING_MODE_FULLSCREEN
+ && otherWindowingMode == WINDOWING_MODE_FREEFORM)
|| isUndefinedMode || isOtherUndefinedMode;
return isCompatibleType && isCompatibleMode;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index c06efc775403..0c795eaa5fcd 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -23,7 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
@@ -31,6 +31,7 @@ import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
+import android.os.Binder;
import android.util.Slog;
import com.android.internal.protolog.ProtoLog;
@@ -154,7 +155,8 @@ class RecentsAnimation {
.setCallingUid(mRecentsUid)
.setCallingPackage(mRecentsComponent.getPackageName())
.setCallingFeatureId(mRecentsFeatureId)
- .setActivityOptions(new SafeActivityOptions(options))
+ .setActivityOptions(new SafeActivityOptions(options,
+ Binder.getCallingPid(), Binder.getCallingUid()))
.setUserId(mUserId)
.execute();
}
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 8cab7d979d07..5ce8a32aa141 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,8 +19,6 @@ package com.android.server.wm;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
-
import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.Display.Mode;
@@ -60,7 +58,6 @@ class RefreshRatePolicy {
}
private final DisplayInfo mDisplayInfo;
- private final Mode mDefaultMode;
private final Mode mLowRefreshRateMode;
private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -92,8 +89,7 @@ class RefreshRatePolicy {
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
mDisplayInfo = displayInfo;
- mDefaultMode = displayInfo.getDefaultMode();
- mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
+ mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -102,7 +98,8 @@ class RefreshRatePolicy {
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
- private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
+ private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
+ final Mode defaultMode = displayInfo.getDefaultMode();
float[] refreshRates = displayInfo.getDefaultRefreshRates();
float bestRefreshRate = defaultMode.getRefreshRate();
mMinSupportedRefreshRate = bestRefreshRate;
@@ -135,33 +132,6 @@ class RefreshRatePolicy {
// Unspecified, use default mode.
return 0;
}
-
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate. But if the display size of default mode is different
- // from the using preferred mode, then still keep the preferred mode to avoid disturbing
- // the animation.
- if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
- Display.Mode preferredMode = null;
- for (Display.Mode mode : mDisplayInfo.supportedModes) {
- if (preferredDisplayModeId == mode.getModeId()) {
- preferredMode = mode;
- break;
- }
- }
- if (preferredMode != null) {
- final int pW = preferredMode.getPhysicalWidth();
- final int pH = preferredMode.getPhysicalHeight();
- if ((pW != mDefaultMode.getPhysicalWidth()
- || pH != mDefaultMode.getPhysicalHeight())
- && pW == mDisplayInfo.getNaturalWidth()
- && pH == mDisplayInfo.getNaturalHeight()) {
- // Prefer not to change display size when animating.
- return preferredDisplayModeId;
- }
- }
- return 0;
- }
-
return preferredDisplayModeId;
}
@@ -264,9 +234,9 @@ class RefreshRatePolicy {
return w.mFrameRateVote.reset();
}
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate.
- if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
+ // If insets animation is running, do not convey the preferred app refresh rate to let VRI
+ // to control the refresh rate.
+ if (w.isInsetsAnimationRunning()) {
return w.mFrameRateVote.reset();
}
@@ -308,7 +278,7 @@ class RefreshRatePolicy {
float getPreferredMinRefreshRate(WindowState w) {
// If app is animating, it's not able to control refresh rate because we want the animation
// to run in default refresh rate.
- if (w.isAnimationRunningSelfOrParent()) {
+ if (w.isAnimationRunningSelfOrParent() || w.isInsetsAnimationRunning()) {
return 0;
}
@@ -331,7 +301,7 @@ class RefreshRatePolicy {
float getPreferredMaxRefreshRate(WindowState w) {
// If app is animating, it's not able to control refresh rate because we want the animation
// to run in default refresh rate.
- if (w.isAnimationRunningSelfOrParent()) {
+ if (w.isAnimationRunningSelfOrParent() || w.isInsetsAnimationRunning()) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 432089ff2fcf..b3b2c57550e4 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
index e4962bffc570..0c1641c8073b 100644
--- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 243dbc78998d..0b48615146b6 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import android.app.ActivityOptions;
import android.content.Intent;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 84072e26761a..46312aff1fb6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -38,13 +38,14 @@ import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_WAKE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_KEEP_SCREEN_ON;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SLEEP_TOKEN;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -104,8 +105,10 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayBrightnessOverrideRequest;
import android.hardware.power.Mode;
import android.net.Uri;
import android.os.Binder;
@@ -152,6 +155,7 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.Slogf;
+import com.android.server.wm.utils.RegionUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -182,8 +186,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
private static final long SLEEP_TRANSITION_WAIT_MILLIS = 1000L;
private Object mLastWindowFreezeSource = null;
- private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- private CharSequence mScreenBrightnessOverrideTag;
+ // Per-display WindowManager overrides that are passed on.
+ private final SparseArray<DisplayBrightnessOverrideRequest> mDisplayBrightnessOverrides =
+ new SparseArray<>();
private long mUserActivityTimeout = -1;
private boolean mUpdateRotation = false;
// Only set while traversing the default display based on its content.
@@ -269,6 +274,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
private boolean mTaskLayersChanged = true;
private int mTmpTaskLayerRank;
private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
+ private Region mTmpOccludingRegion;
+ private Region mTmpTaskRegion;
private String mDestroyAllActivitiesReason;
private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@@ -770,8 +777,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
}
- mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mScreenBrightnessOverrideTag = null;
+ mDisplayBrightnessOverrides.clear();
mUserActivityTimeout = -1;
mObscureApplicationContentOnSecondaryDisplays = false;
mSustainedPerformanceModeCurrent = false;
@@ -874,18 +880,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
if (!mWmService.mDisplayFrozen) {
- final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN
- || mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX
- ? PowerManager.BRIGHTNESS_INVALID_FLOAT : mScreenBrightnessOverride;
- CharSequence overrideTag = null;
- if (brightnessOverride != PowerManager.BRIGHTNESS_INVALID_FLOAT) {
- overrideTag = mScreenBrightnessOverrideTag;
- }
- int brightnessFloatAsIntBits = Float.floatToIntBits(brightnessOverride);
// Post these on a handler such that we don't call into power manager service while
// holding the window manager lock to avoid lock contention with power manager lock.
- mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightnessFloatAsIntBits,
- 0, overrideTag).sendToTarget();
+ mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
+ .sendToTarget();
mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
}
@@ -1038,10 +1036,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
if (w.isDrawn() || (w.mActivityRecord != null && w.mActivityRecord.firstWindowDrawn
&& w.mActivityRecord.isVisibleRequested())) {
- if (!syswin && w.mAttrs.screenBrightness >= 0
- && Float.isNaN(mScreenBrightnessOverride)) {
- mScreenBrightnessOverride = w.mAttrs.screenBrightness;
- mScreenBrightnessOverrideTag = w.getWindowTag();
+ if (!syswin && w.mAttrs.screenBrightness >= PowerManager.BRIGHTNESS_MIN
+ && w.mAttrs.screenBrightness <= PowerManager.BRIGHTNESS_MAX
+ && !mDisplayBrightnessOverrides.contains(w.getDisplayId())) {
+ var brightnessOverride = new DisplayBrightnessOverrideRequest();
+ brightnessOverride.brightness = w.mAttrs.screenBrightness;
+ brightnessOverride.tag = w.getWindowTag();
+ mDisplayBrightnessOverrides.put(w.getDisplayId(), brightnessOverride);
}
// This function assumes that the contents of the default display are processed first
@@ -1113,8 +1114,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_SCREEN_BRIGHTNESS_OVERRIDE:
- mWmService.mPowerManagerInternal.setScreenBrightnessOverrideFromWindowManager(
- Float.intBitsToFloat(msg.arg1), (CharSequence) msg.obj);
+ var brightnessOverrides =
+ (SparseArray<DisplayBrightnessOverrideRequest>) msg.obj;
+ mWmService.mDisplayManagerInternal.setScreenBrightnessOverrideFromWindowManager(
+ brightnessOverrides);
break;
case SET_USER_ACTIVITY_TIMEOUT:
mWmService.mPowerManagerInternal.
@@ -1406,7 +1409,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY;
}
- final DisplayContent display = getDisplayContent(displayId);
+ final DisplayContent display = getDisplayContentOrCreate(displayId);
return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
allowInstrumenting, fromHomeKey),
@@ -2850,6 +2853,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
void prepareForShutdown() {
+ mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
for (int i = 0; i < getChildCount(); i++) {
createSleepToken("shutdown", getChildAt(i).mDisplayId);
}
@@ -2867,7 +2871,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
token = new SleepToken(tag, displayId);
mSleepTokens.put(tokenKey, token);
display.mAllSleepTokens.add(token);
- ProtoLog.d(WM_DEBUG_STATES, "Create sleep token: tag=%s, displayId=%d", tag, displayId);
+ ProtoLog.d(WM_DEBUG_SLEEP_TOKEN, "Create SleepToken: tag=%s, displayId=%d",
+ tag, displayId);
} else {
throw new RuntimeException("Create the same sleep token twice: " + token);
}
@@ -2886,8 +2891,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return;
}
- ProtoLog.d(WM_DEBUG_STATES, "Remove sleep token: tag=%s, displayId=%d", token.mTag,
- token.mDisplayId);
+ ProtoLog.d(WM_DEBUG_SLEEP_TOKEN, "Remove SleepToken: tag=%s, displayId=%d",
+ token.mTag, token.mDisplayId);
display.mAllSleepTokens.remove(token);
if (display.mAllSleepTokens.isEmpty()) {
mService.updateSleepIfNeededLocked();
@@ -2919,6 +2924,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
});
}
+ void invalidateTaskLayersAndUpdateOomAdjIfNeeded() {
+ mRankTaskLayersRunnable.mCheckUpdateOomAdj = true;
+ invalidateTaskLayers();
+ }
+
void invalidateTaskLayers() {
if (!mTaskLayersChanged) {
mTaskLayersChanged = true;
@@ -2936,13 +2946,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Only rank for leaf tasks because the score of activity is based on immediate parent.
forAllLeafTasks(task -> {
final int oldRank = task.mLayerRank;
- final ActivityRecord r = task.topRunningActivityLocked();
- if (r != null && r.isVisibleRequested()) {
+ final int oldRatio = task.mNonOccludedFreeformAreaRatio;
+ task.mNonOccludedFreeformAreaRatio = 0;
+ if (task.isVisibleRequested()) {
task.mLayerRank = ++mTmpTaskLayerRank;
+ if (task.inFreeformWindowingMode()) {
+ computeNonOccludedFreeformAreaRatio(task);
+ }
} else {
task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
}
- if (task.mLayerRank != oldRank) {
+ if (task.mLayerRank != oldRank
+ || task.mNonOccludedFreeformAreaRatio != oldRatio) {
task.forAllActivities(activity -> {
if (activity.hasProcess()) {
mTaskSupervisor.onProcessActivityStateChanged(activity.app,
@@ -2951,10 +2966,40 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
});
}
}, true /* traverseTopToBottom */);
-
+ if (mTmpOccludingRegion != null) {
+ mTmpOccludingRegion.setEmpty();
+ }
+ boolean changed = false;
if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
- mTaskSupervisor.computeProcessActivityStateBatch();
+ changed = mTaskSupervisor.computeProcessActivityStateBatch();
}
+ if (mRankTaskLayersRunnable.mCheckUpdateOomAdj) {
+ mRankTaskLayersRunnable.mCheckUpdateOomAdj = false;
+ if (changed) {
+ mService.updateOomAdj();
+ }
+ }
+ }
+
+ /** This method is called for visible freeform task from top to bottom. */
+ private void computeNonOccludedFreeformAreaRatio(@NonNull Task task) {
+ if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+ return;
+ }
+ if (mTmpOccludingRegion == null) {
+ mTmpOccludingRegion = new Region();
+ mTmpTaskRegion = new Region();
+ }
+ final Rect taskBounds = task.getBounds();
+ mTmpTaskRegion.set(taskBounds);
+ // Exclude the area outside the display.
+ mTmpTaskRegion.op(task.mDisplayContent.getBounds(), Region.Op.INTERSECT);
+ // Exclude the area covered by the above tasks.
+ mTmpTaskRegion.op(mTmpOccludingRegion, Region.Op.DIFFERENCE);
+ task.mNonOccludedFreeformAreaRatio = 100 * RegionUtils.getAreaSize(mTmpTaskRegion)
+ / (taskBounds.width() * taskBounds.height());
+ // Accumulate the occluding region for other visible tasks behind.
+ mTmpOccludingRegion.op(taskBounds, Region.Op.UNION);
}
void clearOtherAppTimeTrackers(AppTimeTracker except) {
@@ -3860,6 +3905,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
private class RankTaskLayersRunnable implements Runnable {
+ boolean mCheckUpdateOomAdj;
+
@Override
public void run() {
synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 8c7b6375a72f..88e534351e2e 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -70,19 +70,6 @@ public class SafeActivityOptions {
private @Nullable ActivityOptions mCallerOptions;
/**
- * Constructs a new instance from a bundle and records {@link Binder#getCallingPid}/
- * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing
- * this object.
- *
- * @param bOptions The {@link ActivityOptions} as {@link Bundle}.
- */
- public static SafeActivityOptions fromBundle(Bundle bOptions) {
- return bOptions != null
- ? new SafeActivityOptions(ActivityOptions.fromBundle(bOptions))
- : null;
- }
-
- /**
* Constructs a new instance from a bundle and provided pid/uid.
*
* @param bOptions The {@link ActivityOptions} as {@link Bundle}.
@@ -95,24 +82,11 @@ public class SafeActivityOptions {
}
/**
- * Constructs a new instance and records {@link Binder#getCallingPid}/
- * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing
- * this object.
- *
- * @param options The options to wrap.
- */
- public SafeActivityOptions(@Nullable ActivityOptions options) {
- mOriginalCallingPid = Binder.getCallingPid();
- mOriginalCallingUid = Binder.getCallingUid();
- mOriginalOptions = options;
- }
-
- /**
* Constructs a new instance.
*
* @param options The options to wrap.
*/
- private SafeActivityOptions(@Nullable ActivityOptions options, int callingPid, int callingUid) {
+ public SafeActivityOptions(@Nullable ActivityOptions options, int callingPid, int callingUid) {
mOriginalCallingPid = callingPid;
mOriginalCallingUid = callingUid;
mOriginalOptions = options;
@@ -158,12 +132,12 @@ public class SafeActivityOptions {
/**
* Overrides options with options from a caller and records {@link Binder#getCallingPid}/
- * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when calling this
- * method.
+ * {@link Binder#getCallingUid}.
*/
- public void setCallerOptions(@Nullable ActivityOptions options) {
- mRealCallingPid = Binder.getCallingPid();
- mRealCallingUid = Binder.getCallingUid();
+ public void setCallerOptions(@Nullable ActivityOptions options, int callingPid,
+ int callingUid) {
+ mRealCallingPid = callingPid;
+ mRealCallingUid = callingUid;
mCallerOptions = options;
}
@@ -387,10 +361,13 @@ public class SafeActivityOptions {
}
// If launched from bubble is specified, then ensure that the caller is system or sysui.
- if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) {
+ if ((options.getLaunchedFromBubble() || options.getTaskAlwaysOnTop())
+ && !isSystemOrSystemUI(callingPid, callingUid)) {
final String msg = "Permission Denial: starting " + getIntentString(intent)
+ " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ") with launchedFromBubble=true";
+ + ", uid=" + callingUid + ") with"
+ + (options.getLaunchedFromBubble() ? " launchedFromBubble=true" : "")
+ + (options.getTaskAlwaysOnTop() ? " taskAlwaysOnTop=true" : "");
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index e1b6e5d0e706..efc68aac0323 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.content.Context.MEDIA_PROJECTION_SERVICE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjectionWatcherCallback;
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index db0374e52b1a..4bd294be22b6 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -20,8 +20,8 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.RotationUtils.deltaRotation;
import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
import static com.android.server.wm.AnimationSpecProto.ROTATE;
import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
@@ -32,7 +32,6 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATI
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
-import static com.android.window.flags.Flags.deleteCaptureDisplay;
import android.animation.ArgbEvaluator;
import android.content.Context;
@@ -41,11 +40,9 @@ import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.os.IBinder;
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.DisplayAddress;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -58,7 +55,6 @@ import android.window.ScreenCapture;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLog;
-import com.android.server.display.DisplayControl;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -171,33 +167,7 @@ class ScreenRotationAnimation {
try {
final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
- if (isSizeChanged && !deleteCaptureDisplay()) {
- final DisplayAddress address = displayInfo.address;
- if (!(address instanceof DisplayAddress.Physical)) {
- Slog.e(TAG, "Display does not have a physical address: " + displayId);
- return;
- }
- final DisplayAddress.Physical physicalAddress =
- (DisplayAddress.Physical) address;
- final IBinder displayToken = DisplayControl.getPhysicalDisplayToken(
- physicalAddress.getPhysicalDisplayId());
- if (displayToken == null) {
- Slog.e(TAG, "Display token is null.");
- return;
- }
- // Temporarily not skip screenshot for the rounded corner overlays and screenshot
- // the whole display to include the rounded corner overlays.
- setSkipScreenshotForRoundedCornerOverlays(false, t);
- mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays();
- final ScreenCapture.DisplayCaptureArgs captureArgs =
- new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setAllowProtected(true)
- .setCaptureSecureLayers(true)
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureDisplay(captureArgs);
- } else if (isSizeChanged) {
+ if (isSizeChanged) {
// Temporarily not skip screenshot for the rounded corner overlays and screenshot
// the whole display to include the rounded corner overlays.
setSkipScreenshotForRoundedCornerOverlays(false, t);
@@ -815,8 +785,7 @@ class ScreenRotationAnimation {
if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
// It also invokes kill().
mDisplayContent.setRotationAnimation(null);
- mDisplayContent.mAppCompatCameraPolicy
- .onScreenRotationAnimationFinished();
+ mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
} else {
kill();
}
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index c20b85858c44..8f0f6860ceb6 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -56,7 +56,7 @@ public class SeamlessRotator {
mOldRotation = oldRotation;
mNewRotation = newRotation;
mApplyFixedTransformHint = applyFixedTransformationHint;
- mFixedTransformHint = oldRotation;
+ mFixedTransformHint = (oldRotation + info.installOrientation) % 4;
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
final int pH = flipped ? info.logicalWidth : info.logicalHeight;
final int pW = flipped ? info.logicalHeight : info.logicalWidth;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index d295378a9b65..07de489d9ff4 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -37,7 +37,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -706,6 +706,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
win.setRequestedVisibleTypes(requestedVisibleTypes);
win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
imeStatsToken);
+ final Task task = win.getTask();
+ if (task != null) {
+ task.dispatchTaskInfoChangedIfNeeded(/* forced= */ true);
+ }
} else {
EmbeddedWindowController.EmbeddedWindow embeddedWindow = null;
if (android.view.inputmethod.Flags.refactorInsetsController()) {
@@ -715,7 +719,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (embeddedWindow != null) {
// If there is no WindowState for the IWindow, it could be still an
// EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
- // TODO(b/329229469) Use different phase here
+ // TODO(b/353463205) Use different phase here
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
embeddedWindow.setRequestedVisibleTypes(
@@ -1008,4 +1012,15 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
}
+
+ @Override
+ public void notifyInsetsAnimationRunningStateChanged(IWindow window, boolean running) {
+ synchronized (mService.mGlobalLock) {
+ final WindowState win = mService.windowForClientLocked(this, window,
+ false /* throwOnError */);
+ if (win != null) {
+ win.notifyInsetsAnimationRunningStateChanged(running);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 52994c70f183..3ee2e6048634 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -152,7 +152,10 @@ class SnapshotController {
if (mOpenActivities.isEmpty()) {
return false;
}
- if (Flags.alwaysCaptureActivitySnapshot()) {
+ // TODO (b/362183912) always capture activity snapshot will cause performance
+ // regression, remove flag after ramp up
+ if (!Flags.deferPredictiveAnimationIfNoSnapshot()
+ && Flags.alwaysCaptureActivitySnapshot()) {
return true;
}
for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 67f4e563a2b1..3eb13c52cca6 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -50,7 +50,7 @@ import java.util.ArrayDeque;
class SnapshotPersistQueue {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
private static final long DELAY_MS = 100;
- private static final int MAX_STORE_QUEUE_DEPTH = 2;
+ static final int MAX_STORE_QUEUE_DEPTH = 2;
private static final int COMPRESS_QUALITY = 95;
@GuardedBy("mLock")
@@ -64,6 +64,7 @@ class SnapshotPersistQueue {
private boolean mStarted;
private final Object mLock = new Object();
private final UserManagerInternal mUserManagerInternal;
+ private boolean mShutdown;
SnapshotPersistQueue() {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -101,6 +102,46 @@ class SnapshotPersistQueue {
}
}
+ /**
+ * Prepare to enqueue all visible task snapshots because of shutdown.
+ */
+ void prepareShutdown() {
+ synchronized (mLock) {
+ mShutdown = true;
+ }
+ }
+
+ private boolean isQueueEmpty() {
+ synchronized (mLock) {
+ return mWriteQueue.isEmpty() || mQueueIdling || mPaused;
+ }
+ }
+
+ void waitFlush(long timeout) {
+ if (timeout <= 0) {
+ return;
+ }
+ final long endTime = System.currentTimeMillis() + timeout;
+ while (true) {
+ if (!isQueueEmpty()) {
+ long timeRemaining = endTime - System.currentTimeMillis();
+ if (timeRemaining > 0) {
+ synchronized (mLock) {
+ try {
+ mLock.wait(timeRemaining);
+ } catch (InterruptedException e) {
+ }
+ }
+ } else {
+ Slog.w(TAG, "Snapshot Persist Queue flush timed out");
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
@VisibleForTesting
void waitForQueueEmpty() {
while (true) {
@@ -113,7 +154,12 @@ class SnapshotPersistQueue {
}
}
- @VisibleForTesting
+ int peekWriteQueueSize() {
+ synchronized (mLock) {
+ return mStoreQueueItems.size();
+ }
+ }
+
int peekQueueSize() {
synchronized (mLock) {
return mWriteQueue.size();
@@ -128,7 +174,9 @@ class SnapshotPersistQueue {
mWriteQueue.addLast(item);
}
item.onQueuedLocked();
- ensureStoreQueueDepthLocked();
+ if (!mShutdown) {
+ ensureStoreQueueDepthLocked();
+ }
if (!mPaused) {
mLock.notifyAll();
}
@@ -193,13 +241,18 @@ class SnapshotPersistQueue {
if (isReadyToWrite) {
next.write();
}
- SystemClock.sleep(DELAY_MS);
+ if (!mShutdown) {
+ SystemClock.sleep(DELAY_MS);
+ }
}
synchronized (mLock) {
final boolean writeQueueEmpty = mWriteQueue.isEmpty();
if (!writeQueueEmpty && !mPaused) {
continue;
}
+ if (mShutdown && writeQueueEmpty) {
+ mLock.notifyAll();
+ }
try {
mQueueIdling = writeQueueEmpty;
mLock.wait();
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 57f9be097ee6..9a48d5b8880d 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -16,9 +16,8 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_ADAPTER;
-import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED;
import static com.android.server.wm.SurfaceAnimatorProto.LEASH;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -90,8 +89,6 @@ public class SurfaceAnimator {
@Nullable
private Runnable mAnimationCancelledCallback;
- private boolean mAnimationStartDelayed;
-
private boolean mAnimationFinished;
/**
@@ -188,10 +185,6 @@ public class SurfaceAnimator {
mAnimatable.onAnimationLeashCreated(t, mLeash);
}
mAnimatable.onLeashAnimationStarting(t, mLeash);
- if (mAnimationStartDelayed) {
- ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
- return;
- }
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
StringWriter sw = new StringWriter();
@@ -215,36 +208,7 @@ public class SurfaceAnimator {
null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
}
- /**
- * Begins with delaying all animations to start. Any subsequent call to {@link #startAnimation}
- * will not start the animation until {@link #endDelayingAnimationStart} is called. When an
- * animation start is being delayed, the animator is considered animating already.
- */
- void startDelayingAnimationStart() {
-
- // We only allow delaying animation start we are not currently animating
- if (!isAnimating()) {
- mAnimationStartDelayed = true;
- }
- }
-
- /**
- * See {@link #startDelayingAnimationStart}.
- */
- void endDelayingAnimationStart() {
- final boolean delayed = mAnimationStartDelayed;
- mAnimationStartDelayed = false;
- if (delayed && mAnimation != null) {
- mAnimation.startAnimation(mLeash, mAnimatable.getSyncTransaction(),
- mAnimationType, mInnerAnimationFinishedCallback);
- mAnimatable.commitPendingTransaction();
- }
- }
-
- /**
- * @return Whether we are currently running an animation, or we have a pending animation that
- * is waiting to be started with {@link #endDelayingAnimationStart}
- */
+ /** Returns whether it is currently running an animation. */
boolean isAnimating() {
return mAnimation != null;
}
@@ -290,15 +254,6 @@ public class SurfaceAnimator {
}
/**
- * Reparents the surface.
- *
- * @see #setLayer
- */
- void reparent(Transaction t, SurfaceControl newParent) {
- t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
- }
-
- /**
* @return True if the surface is attached to the leash; false otherwise.
*/
boolean hasLeash() {
@@ -319,7 +274,6 @@ public class SurfaceAnimator {
Slog.w(TAG, "Unable to transfer animation, because " + from + " animation is finished");
return;
}
- endDelayingAnimationStart();
final Transaction t = mAnimatable.getSyncTransaction();
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mLeash = from.mLeash;
@@ -336,10 +290,6 @@ public class SurfaceAnimator {
mService.mAnimationTransferMap.put(mAnimation, this);
}
- boolean isAnimationStartDelayed() {
- return mAnimationStartDelayed;
- }
-
/**
* Cancels the animation, and resets the leash.
*
@@ -361,7 +311,7 @@ public class SurfaceAnimator {
final SurfaceFreezer.Snapshot snapshot = mSnapshot;
reset(t, false);
if (animation != null) {
- if (!mAnimationStartDelayed && forwardCancel) {
+ if (forwardCancel) {
animation.onAnimationCancelled(leash);
if (animationCancelledCallback != null) {
animationCancelledCallback.run();
@@ -386,10 +336,6 @@ public class SurfaceAnimator {
mService.scheduleAnimationLocked();
}
}
-
- if (!restarting) {
- mAnimationStartDelayed = false;
- }
}
private void reset(Transaction t, boolean destroyLeash) {
@@ -495,14 +441,12 @@ public class SurfaceAnimator {
if (mLeash != null) {
mLeash.dumpDebug(proto, LEASH);
}
- proto.write(ANIMATION_START_DELAYED, mAnimationStartDelayed);
proto.end(token);
}
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
- pw.print(" mAnimationType=" + animationTypeToString(mAnimationType));
- pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : "");
+ pw.print(" mAnimationType="); pw.println(animationTypeToString(mAnimationType));
pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation);
if (mAnimation != null) {
mAnimation.dump(pw, prefix + " ");
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 34abf23daa2a..e126ed65d508 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6007008e9f3f..c8befb21fe13 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -62,10 +62,11 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_LOCKTASK;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -127,7 +128,6 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -169,6 +169,7 @@ import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ITaskOrganizer;
import android.window.PictureInPictureSurfaceTransaction;
@@ -254,9 +255,6 @@ class Task extends TaskFragment {
private static final String ATTR_MIN_HEIGHT = "min_height";
private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version";
private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity";
- private static final String ATTR_LAST_SNAPSHOT_TASK_SIZE = "last_snapshot_task_size";
- private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets";
- private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size";
// How long to wait for all background Activities to redraw following a call to
// convertToTranslucent().
@@ -430,6 +428,9 @@ class Task extends TaskFragment {
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = LAYER_RANK_INVISIBLE;
+ /** A 0~100 ratio to indicate the percentage of visible area on screen of a freeform task. */
+ int mNonOccludedFreeformAreaRatio;
+
/* Unique identifier for this task. */
final int mTaskId;
/* User for which this task was created. */
@@ -467,10 +468,6 @@ class Task extends TaskFragment {
// NOTE: This value needs to be persisted with each task
private TaskDescription mTaskDescription;
- // Information about the last snapshot that should be persisted with the task to allow SystemUI
- // to layout without loading all the task snapshots
- final PersistedTaskSnapshotData mLastTaskSnapshotData;
-
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -501,6 +498,11 @@ class Task extends TaskFragment {
boolean mIsTrimmableFromRecents;
/**
+ * Sets whether the launch-adjacent flag is respected or not for this task or its child tasks.
+ */
+ private boolean mLaunchAdjacentDisabled;
+
+ /**
* Bounds offset should be applied when calculating compatible configuration for apps targeting
* SDK level 34 or before.
*/
@@ -625,14 +627,13 @@ class Task extends TaskFragment {
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
boolean _autoRemoveRecents, int _userId, int _effectiveUid,
String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
- TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
- int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
- String callingPackage, @Nullable String callingFeatureId, int resizeMode,
- boolean supportsPictureInPicture, boolean _realActivitySuspended,
- boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info,
- IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
- boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear,
- boolean _removeWithTaskOrganizer) {
+ TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
+ int nextTaskId, int callingUid, String callingPackage,
+ @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture,
+ boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
+ ActivityInfo info, IVoiceInteractionSession _voiceSession,
+ IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer, IBinder _launchCookie,
+ boolean _deferTaskAppear, boolean _removeWithTaskOrganizer) {
super(atmService, null /* fragmentToken */, _createdByOrganizer, false /* isEmbedded */);
mTaskId = _taskId;
@@ -642,9 +643,6 @@ class Task extends TaskFragment {
mTaskDescription = _lastTaskDescription != null
? _lastTaskDescription
: new TaskDescription();
- mLastTaskSnapshotData = _lastSnapshotData != null
- ? _lastSnapshotData
- : new PersistedTaskSnapshotData();
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
@@ -1200,6 +1198,31 @@ class Task extends TaskFragment {
}
@Override
+ void onResize() {
+ super.onResize();
+ onTaskBoundsChangedForFreeform();
+ }
+
+ @Override
+ void onMovedByResize() {
+ super.onMovedByResize();
+ onTaskBoundsChangedForFreeform();
+ }
+
+ private void onTaskBoundsChangedForFreeform() {
+ if (!isVisibleRequested() || !inFreeformWindowingMode()) {
+ return;
+ }
+
+ mAtmService.notifyTaskPersisterLocked(this, false /* flush */);
+
+ if (!com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()) {
+ return;
+ }
+ mRootWindowContainer.invalidateTaskLayersAndUpdateOomAdjIfNeeded();
+ }
+
+ @Override
@Nullable
ActivityRecord getTopResumedActivity() {
if (!isLeafTask()) {
@@ -3079,7 +3102,6 @@ class Task extends TaskFragment {
}
void onSnapshotChanged(TaskSnapshot snapshot) {
- mLastTaskSnapshotData.set(snapshot);
mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(
mTaskId, snapshot);
}
@@ -3265,22 +3287,25 @@ class Task extends TaskFragment {
mDimmer.resetDimStates();
super.prepareSurfaces();
- final Rect dimBounds = mDimmer.getDimBounds();
- if (dimBounds != null) {
- getDimBounds(dimBounds);
+ Rect dimBounds = null;
+ if (!Flags.useTasksDimOnly()) {
+ dimBounds = mDimmer.getDimBounds();
+ if (dimBounds != null) {
+ getDimBounds(dimBounds);
- // Bounds need to be relative, as the dim layer is a child.
- if (inFreeformWindowingMode()) {
- getBounds(mTmpRect);
- dimBounds.offsetTo(dimBounds.left - mTmpRect.left, dimBounds.top - mTmpRect.top);
- } else {
- dimBounds.offsetTo(0, 0);
+ // Bounds need to be relative, as the dim layer is a child.
+ if (inFreeformWindowingMode()) {
+ getBounds(mTmpRect);
+ dimBounds.offset(-mTmpRect.left, -mTmpRect.top);
+ } else {
+ dimBounds.offsetTo(0, 0);
+ }
}
}
final SurfaceControl.Transaction t = getSyncTransaction();
- if (dimBounds != null && mDimmer.updateDims(t)) {
+ if (mDimmer.hasDimState() && mDimmer.updateDims(t)) {
scheduleAnimation();
}
@@ -3388,6 +3413,8 @@ class Task extends TaskFragment {
? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
+ info.topActivityRequestOpenInBrowserEducationTimestamp = top != null
+ ? top.mRequestOpenInBrowserEducationTimestamp : 0;
final Task parentTask = getParent() != null ? getParent().asTask() : null;
info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer
? parentTask.mTaskId
@@ -3395,11 +3422,47 @@ class Task extends TaskFragment {
info.isFocused = isFocused();
info.isVisible = hasVisibleChildren();
info.isVisibleRequested = isVisibleRequested();
+ info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
- info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
+ info.isActivityStackTransparent = !topTask.forAllActivities(r -> (r.occludesParent()));
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
+ final WindowState windowState = top != null
+ ? top.findMainWindow(/* includeStartingApp= */ false) : null;
+ info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
+ ? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
+ info.topActivityMainWindowFrame = calculateTopActivityMainWindowFrameForTaskInfo(top);
+ }
+
+ /**
+ * Returns the top activity's main window frame if it doesn't match
+ * {@link ActivityRecord#getBounds() the top activity bounds}, or {@code null}, otherwise.
+ *
+ * @param top The top running activity of the task
+ */
+ @Nullable
+ private static Rect calculateTopActivityMainWindowFrameForTaskInfo(
+ @Nullable ActivityRecord top) {
+ if (!Flags.betterSupportNonMatchParentActivity()) {
+ return null;
+ }
+ if (top == null) {
+ return null;
+ }
+ final WindowState mainWindow = top.findMainWindow();
+ if (mainWindow == null) {
+ return null;
+ }
+ if (!mainWindow.mHaveFrame) {
+ return null;
+ }
+ final Rect windowFrame = mainWindow.getFrame();
+ final Rect parentFrame = mainWindow.getParentFrame();
+ if (parentFrame.equals(windowFrame)) {
+ return null;
+ }
+ return windowFrame;
}
/**
@@ -3451,6 +3514,7 @@ class Task extends TaskFragment {
info.capturedLink = null;
info.capturedLinkTimestamp = 0;
+ info.topActivityRequestOpenInBrowserEducationTimestamp = 0;
}
@Nullable PictureInPictureParams getPictureInPictureParams() {
@@ -3797,6 +3861,12 @@ class Task extends TaskFragment {
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents);
+ if (mLaunchAdjacentDisabled) {
+ pw.println(prefix + "mLaunchAdjacentDisabled=true");
+ }
+ if (mReparentLeafTaskIfRelaunch) {
+ pw.println(prefix + "mReparentLeafTaskIfRelaunch=true");
+ }
}
@Override
@@ -3844,9 +3914,13 @@ class Task extends TaskFragment {
sb.append(" aI=");
sb.append(affinityIntent.getComponent().flattenToShortString());
}
- sb.append(" isResizeable=").append(isResizeable());
- sb.append(" minWidth=").append(mMinWidth);
- sb.append(" minHeight=").append(mMinHeight);
+ if (!isResizeable()) {
+ sb.append(" nonResizable");
+ }
+ if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
+ sb.append(" minWidth=").append(mMinWidth);
+ sb.append(" minHeight=").append(mMinHeight);
+ }
sb.append('}');
return stringName = sb.toString();
}
@@ -3910,19 +3984,6 @@ class Task extends TaskFragment {
out.attributeInt(null, ATTR_MIN_HEIGHT, mMinHeight);
out.attributeInt(null, ATTR_PERSIST_TASK_VERSION, PERSIST_TASK_VERSION);
- if (mLastTaskSnapshotData.taskSize != null) {
- out.attribute(null, ATTR_LAST_SNAPSHOT_TASK_SIZE,
- mLastTaskSnapshotData.taskSize.flattenToString());
- }
- if (mLastTaskSnapshotData.contentInsets != null) {
- out.attribute(null, ATTR_LAST_SNAPSHOT_CONTENT_INSETS,
- mLastTaskSnapshotData.contentInsets.flattenToString());
- }
- if (mLastTaskSnapshotData.bufferSize != null) {
- out.attribute(null, ATTR_LAST_SNAPSHOT_BUFFER_SIZE,
- mLastTaskSnapshotData.bufferSize.flattenToString());
- }
-
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
affinityIntent.saveToXml(out);
@@ -3989,7 +4050,6 @@ class Task extends TaskFragment {
int taskId = INVALID_TASK_ID;
final int outerDepth = in.getDepth();
TaskDescription taskDescription = new TaskDescription();
- PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
int taskAffiliation = INVALID_TASK_ID;
int prevTaskId = INVALID_TASK_ID;
int nextTaskId = INVALID_TASK_ID;
@@ -4096,15 +4156,6 @@ class Task extends TaskFragment {
case ATTR_PERSIST_TASK_VERSION:
persistTaskVersion = Integer.parseInt(attrValue);
break;
- case ATTR_LAST_SNAPSHOT_TASK_SIZE:
- lastSnapshotData.taskSize = Point.unflattenFromString(attrValue);
- break;
- case ATTR_LAST_SNAPSHOT_CONTENT_INSETS:
- lastSnapshotData.contentInsets = Rect.unflattenFromString(attrValue);
- break;
- case ATTR_LAST_SNAPSHOT_BUFFER_SIZE:
- lastSnapshotData.bufferSize = Point.unflattenFromString(attrValue);
- break;
default:
if (!attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
Slog.w(TAG, "Task: Unknown attribute=" + attrName);
@@ -4198,7 +4249,6 @@ class Task extends TaskFragment {
.setLastTimeMoved(lastTimeOnTop)
.setNeverRelinquishIdentity(neverRelinquishIdentity)
.setLastTaskDescription(taskDescription)
- .setLastSnapshotData(lastSnapshotData)
.setTaskAffiliation(taskAffiliation)
.setPrevAffiliateTaskId(prevTaskId)
.setNextAffiliateTaskId(nextTaskId)
@@ -4442,7 +4492,7 @@ class Task extends TaskFragment {
}
void onPictureInPictureParamsChanged() {
- if (inPinnedWindowingMode()) {
+ if (inPinnedWindowingMode() || Flags.enableDesktopWindowingPip()) {
dispatchTaskInfoChangedIfNeeded(true /* force */);
}
}
@@ -4646,7 +4696,7 @@ class Task extends TaskFragment {
}
}
if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN
- && topActivity != null && !topActivity.noDisplay
+ && topActivity != null && !topActivity.isNoDisplay()
&& topActivity.canForceResizeNonResizable(likelyResolvedMode)) {
// Inform the user that they are starting an app that may not work correctly in
// multi-window mode.
@@ -4706,8 +4756,13 @@ class Task extends TaskFragment {
// If the moveToFront is a part of finishing transition, then make sure
// the z-order of tasks are up-to-date.
if (topActivity.mTransitionController.inFinishingTransition(topActivity)) {
- Transition.assignLayers(taskDisplayArea,
- taskDisplayArea.getPendingTransaction());
+ final SurfaceControl.Transaction tx =
+ taskDisplayArea.getPendingTransaction();
+ Transition.assignLayers(taskDisplayArea, tx);
+ final SurfaceControl leash = topActivity.getFixedRotationLeash();
+ if (leash != null) {
+ tx.setLayer(leash, topActivity.getLastLayer());
+ }
}
}
}
@@ -5901,6 +5956,10 @@ class Task extends TaskFragment {
pw.print(prefix); pw.print(" mLastNonFullscreenBounds=");
pw.println(mLastNonFullscreenBounds);
}
+ if (mNonOccludedFreeformAreaRatio != 0) {
+ pw.print(prefix); pw.print(" mNonOccludedFreeformAreaRatio=");
+ pw.println(mNonOccludedFreeformAreaRatio);
+ }
if (isLeafTask()) {
pw.println(prefix + " isSleeping=" + shouldSleepActivities());
printThisActivity(pw, getTopPausingActivity(), dumpPackage, false,
@@ -6183,6 +6242,8 @@ class Task extends TaskFragment {
void maybeApplyLastRecentsAnimationTransaction() {
if (mLastRecentsAnimationTransaction != null) {
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "Applying last recents animation transaction.");
final SurfaceControl.Transaction tx = getPendingTransaction();
if (mLastRecentsAnimationOverlay != null) {
tx.reparent(mLastRecentsAnimationOverlay, mSurfaceControl);
@@ -6235,12 +6296,6 @@ class Task extends TaskFragment {
return mAnimatingActivityRegistry;
}
- @Override
- void executeAppTransition(ActivityOptions options) {
- mDisplayContent.executeAppTransition();
- ActivityOptions.abort(options);
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
@@ -6262,6 +6317,28 @@ class Task extends TaskFragment {
}
/**
+ * Sets this task and its children to disable respecting launch-adjacent.
+ */
+ void setLaunchAdjacentDisabled(boolean disabled) {
+ mLaunchAdjacentDisabled = disabled;
+ }
+
+ /**
+ * Returns whether this task or any of its ancestors have disabled respecting the
+ * launch-adjacent flag.
+ */
+ boolean isLaunchAdjacentDisabled() {
+ Task t = this;
+ while (t != null) {
+ if (t.mLaunchAdjacentDisabled) {
+ return true;
+ }
+ t = t.getParent().asTask();
+ }
+ return false;
+ }
+
+ /**
* Return true if the activityInfo has the same requiredDisplayCategory as this task.
*/
boolean isSameRequiredDisplayCategory(@NonNull ActivityInfo info) {
@@ -6331,7 +6408,6 @@ class Task extends TaskFragment {
private long mLastTimeMoved;
private boolean mNeverRelinquishIdentity;
private TaskDescription mLastTaskDescription;
- private PersistedTaskSnapshotData mLastSnapshotData;
private int mTaskAffiliation;
private int mPrevAffiliateTaskId = INVALID_TASK_ID;
private int mNextAffiliateTaskId = INVALID_TASK_ID;
@@ -6559,11 +6635,6 @@ class Task extends TaskFragment {
return this;
}
- private Builder setLastSnapshotData(PersistedTaskSnapshotData lastSnapshotData) {
- mLastSnapshotData = lastSnapshotData;
- return this;
- }
-
private Builder setOrigActivity(ComponentName origActivity) {
mOrigActivity = origActivity;
return this;
@@ -6712,7 +6783,7 @@ class Task extends TaskFragment {
return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
- mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
+ mNeverRelinquishIdentity, mLastTaskDescription,
mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 586f3c35c0c4..c3649fe98056 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -62,6 +62,8 @@ class TaskChangeNotificationController {
private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 27;
private static final int NOTIFY_LOCK_TASK_MODE_CHANGED_MSG = 28;
private static final int NOTIFY_TASK_SNAPSHOT_INVALIDATED_LISTENERS_MSG = 29;
+ private static final int NOTIFY_RECENT_TASK_REMOVED_FOR_ADD_TASK_LISTENERS_MSG = 30;
+
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -167,6 +169,10 @@ class TaskChangeNotificationController {
l.onRecentTaskListFrozenChanged(m.arg1 != 0);
};
+ private final TaskStackConsumer mNotifyRecentTaskRemovedForAddTask = (l, m) -> {
+ l.onRecentTaskRemovedForAddTask(m.arg1);
+ };
+
private final TaskStackConsumer mNotifyTaskFocusChanged = (l, m) -> {
l.onTaskFocusChanged(m.arg1, m.arg2 != 0);
};
@@ -261,6 +267,9 @@ class TaskChangeNotificationController {
case NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG:
forAllRemoteListeners(mNotifyTaskListFrozen, msg);
break;
+ case NOTIFY_RECENT_TASK_REMOVED_FOR_ADD_TASK_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyRecentTaskRemovedForAddTask, msg);
+ break;
case NOTIFY_TASK_FOCUS_CHANGED_MSG:
forAllRemoteListeners(mNotifyTaskFocusChanged, msg);
break;
@@ -541,6 +550,15 @@ class TaskChangeNotificationController {
msg.sendToTarget();
}
+ /** Called when a task is removed from the recent tasks list. */
+ void notifyRecentTaskRemovedForAddTask(int taskId) {
+ final Message msg = mHandler.obtainMessage(
+ NOTIFY_RECENT_TASK_REMOVED_FOR_ADD_TASK_LISTENERS_MSG, taskId,
+ 0 /* unused */);
+ forAllLocalListeners(mNotifyRecentTaskRemovedForAddTask, msg);
+ msg.sendToTarget();
+ }
+
/** @see ITaskStackListener#onTaskFocusChanged(int, boolean) */
void notifyTaskFocusChanged(int taskId, boolean focused) {
final Message msg = mHandler.obtainMessage(NOTIFY_TASK_FOCUS_CHANGED_MSG,
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 83d08cca43ad..2bec0f414a0c 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -30,7 +30,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.TAG_ROOT_TASK;
import static com.android.server.wm.DisplayContent.alwaysCreateRootTask;
@@ -918,6 +919,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
if (candidateTask.getParent() == null) {
addChild(candidateTask, position);
} else {
+ if (candidateTask.getRootTask().mReparentLeafTaskIfRelaunch) {
+ ProtoLog.d(WM_DEBUG_TASKS, "Reparenting to display area on relaunch: "
+ + "rootTaskId=%d toTop=%b", candidateTask.mTaskId, onTop);
+ }
candidateTask.reparent(this, onTop);
}
}
@@ -1074,6 +1079,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask());
// We only allow this for created by organizer tasks.
if (launchRootTask != null && launchRootTask.mCreatedByOrganizer) {
+ Slog.i(TAG_WM, "Using launch root task from activity options: taskId="
+ + launchRootTask.mTaskId);
return launchRootTask;
}
}
@@ -1081,19 +1088,25 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// Use launch-adjacent-flag-root if launching with launch-adjacent flag.
if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& mLaunchAdjacentFlagRootTask != null) {
+ final Task launchAdjacentRootAdjacentTask =
+ mLaunchAdjacentFlagRootTask.getAdjacentTask();
if (sourceTask != null && (sourceTask == candidateTask
|| sourceTask.topRunningActivity() == null)) {
// Do nothing when task that is getting opened is same as the source or when
// the source is no-longer valid.
Slog.w(TAG_WM, "Ignoring LAUNCH_ADJACENT because adjacent source is gone.");
} else if (sourceTask != null
- && mLaunchAdjacentFlagRootTask.getAdjacentTask() != null
+ && launchAdjacentRootAdjacentTask != null
&& (sourceTask == mLaunchAdjacentFlagRootTask
|| sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) {
- // If the adjacent launch is coming from the same root, launch to
- // adjacent root instead.
- return mLaunchAdjacentFlagRootTask.getAdjacentTask();
+ // If the adjacent launch is coming from the same root that was specified as the
+ // launch-adjacent task, so instead we launch to its adjacent root instead.
+ Slog.i(TAG_WM, "Using adjacent-to specified launch-adjacent task: taskId="
+ + launchAdjacentRootAdjacentTask.mTaskId);
+ return launchAdjacentRootAdjacentTask;
} else {
+ Slog.i(TAG_WM, "Using specified launch-adjacent task: taskId="
+ + mLaunchAdjacentFlagRootTask.mTaskId);
return mLaunchAdjacentFlagRootTask;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f58b322cab36..51b8bd1f0091 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -49,7 +49,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -1090,8 +1090,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
- true /* ignoringKeyguard */) == null;
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
}
/**
@@ -1734,6 +1733,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!hasDirectChildActivities()) {
return false;
}
+ if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) {
+ // Even if the transient activity is occluded, defer pausing (addToStopping will still
+ // be called) it until the transient transition is done. So the current resuming
+ // activity won't need to wait for additional pause complete.
+ return false;
+ }
ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
mResumedActivity);
@@ -2124,7 +2129,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
void executeAppTransition(ActivityOptions options) {
- // No app transition applied to the task fragment.
+ mDisplayContent.executeAppTransition();
+ ActivityOptions.abort(options);
}
@Override
@@ -3027,7 +3033,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// The task order may be changed by finishIfPossible() for adjusting focus if there are
// nested tasks, so add all activities into a list to avoid missed removals.
final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
- forAllActivities((Consumer<ActivityRecord>) removingActivities::add);
+ forAllActivities((r) -> {
+ if (!r.finishing) {
+ removingActivities.add(r);
+ }
+ });
for (int i = removingActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = removingActivities.get(i);
if (withTransition && r.isVisible()) {
@@ -3153,12 +3163,16 @@ class TaskFragment extends WindowContainer<WindowContainer> {
/** Bounds to be used for dimming, as well as touch related tests. */
void getDimBounds(@NonNull Rect out) {
- if (mIsEmbedded && isDimmingOnParentTask() && getDimmer().getDimBounds() != null) {
- // Return the task bounds if the dimmer is showing and should cover on the Task (not
- // just on this embedded TaskFragment).
- out.set(getTask().getBounds());
+ if (Flags.useTasksDimOnly() && mDimmer.hasDimState()) {
+ out.set(mDimmer.getDimBounds());
} else {
- out.set(getBounds());
+ if (mIsEmbedded && isDimmingOnParentTask() && getDimmer().getDimBounds() != null) {
+ // Return the task bounds if the dimmer is showing and should cover on the Task (not
+ // just on this embedded TaskFragment).
+ out.set(getTask().getBounds());
+ } else {
+ out.set(getBounds());
+ }
}
}
@@ -3189,10 +3203,16 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mDimmer.resetDimStates();
super.prepareSurfaces();
- final Rect dimBounds = mDimmer.getDimBounds();
- if (dimBounds != null) {
- // Bounds need to be relative, as the dim layer is a child.
- dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ if (!Flags.useTasksDimOnly()) {
+ final Rect dimBounds = mDimmer.getDimBounds();
+ if (dimBounds != null) {
+ // Bounds need to be relative, as the dim layer is a child.
+ dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ if (mDimmer.updateDims(getSyncTransaction())) {
+ scheduleAnimation();
+ }
+ }
+ } else {
if (mDimmer.updateDims(getSyncTransaction())) {
scheduleAnimation();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 83e714d82dd2..c6a16795dd8c 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -27,7 +27,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHA
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
@@ -61,7 +61,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
@@ -482,7 +482,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
final int transitionId = mWindowOrganizerController.getTransitionController()
.getCollectingTransitionId();
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Defer transition id=%d for TaskFragmentTransaction=%s", transitionId,
transaction.getTransactionToken());
mDeferredTransitions.put(transaction.getTransactionToken(), transitionId);
@@ -503,13 +503,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|| mWindowOrganizerController.getTransitionController()
.getCollectingTransitionId() != transitionId) {
// This can happen when the transition is timeout or abort.
- ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.w(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Deferred transition id=%d has been continued before the"
+ " TaskFragmentTransaction=%s is finished",
transitionId, transactionToken);
return;
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Continue transition id=%d for TaskFragmentTransaction=%s", transitionId,
transactionToken);
mWindowOrganizerController.getTransitionController().continueTransitionReady();
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 5c9a84db002a..c39671d76929 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -449,7 +449,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// If the source activity is a no-display activity, pass on the launch display area token
// from source activity as currently preferred.
- if (taskDisplayArea == null && source != null && source.noDisplay) {
+ if (taskDisplayArea == null && source != null && source.isNoDisplay()) {
taskDisplayArea = source.mHandoverTaskDisplayArea;
if (taskDisplayArea != null) {
if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index de2e4f5474c4..eefc54457a4d 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -21,7 +21,7 @@ import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 1f82cdb70b91..c130931277fe 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -307,6 +307,36 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
}
/**
+ * Record task snapshots before shutdown.
+ */
+ void prepareShutdown() {
+ if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+ return;
+ }
+ // Make write items run in a batch.
+ mPersister.mSnapshotPersistQueue.setPaused(true);
+ mPersister.mSnapshotPersistQueue.prepareShutdown();
+ for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+ mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+ if (task.isVisible() && !task.isActivityTypeHome()) {
+ final TaskSnapshot snapshot = captureSnapshot(task);
+ if (snapshot != null) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ }
+ }
+ }, true /* traverseTopToBottom */);
+ }
+ mPersister.mSnapshotPersistQueue.setPaused(false);
+ }
+
+ void waitFlush(long timeout) {
+ if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+ return;
+ }
+ mPersister.mSnapshotPersistQueue.waitFlush(timeout);
+ }
+
+ /**
* Called when screen is being turned off.
*/
void screenTurningOff(int displayId, ScreenOffListener listener) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1058a2561ddc..43e45e18f01e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -109,7 +109,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -237,15 +237,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
/**
- * Set of transient activities (lifecycle initially tied to this transition) and their
+ * Map of transient activities (lifecycle initially tied to this transition) to their
* restore-below tasks.
*/
private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
/**
* The tasks that may be occluded by the transient activity. Assume the task stack is
- * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
- * task, and [B, C] are the transient-hide tasks.
+ * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), and Home is started in a
+ * transient-launch activity, then A is the restore-below task, and [B, C] are the
+ * transient-hide tasks.
*/
private ArrayList<Task> mTransientHideTasks;
@@ -385,7 +386,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (b == null || b.isEmpty()) continue;
final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH);
if (transientLaunch) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Starting a Recents transition which can be parallel.");
mParallelCollectType = PARALLEL_TYPE_RECENTS;
}
@@ -401,18 +402,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mTransientLaunches.put(activity, restoreBelow);
setTransientLaunchToChanges(activity);
- final Task transientRootTask = activity.getRootTask();
+ final int restoreBelowTaskId = restoreBelow != null ? restoreBelow.mTaskId : -1;
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+ + "transient-launch restoreBelowTaskId=%d", mSyncId, activity, restoreBelowTaskId);
+
+ final Task transientLaunchRootTask = activity.getRootTask();
final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
- : (transientRootTask != null ? transientRootTask.getParent() : null);
+ : (transientLaunchRootTask != null ? transientLaunchRootTask.getParent() : null);
if (parent != null) {
// Collect all visible tasks which can be occluded by the transient activity to
// make sure they are in the participants so their visibilities can be updated when
// finishing transition.
+ // Note: This currently assumes that the parent is a DA containing the full set of
+ // visible tasks
parent.forAllTasks(t -> {
// Skip transient-launch task
- if (t == transientRootTask) return false;
+ if (t == transientLaunchRootTask) return false;
if (t.isVisibleRequested() && !t.isAlwaysOnTop()) {
if (t.isRootTask()) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ " transient hide: taskId=%d", t.mTaskId);
mTransientHideTasks.add(t);
}
if (t.isLeafTask()) {
@@ -442,9 +451,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// the gesture threshold.
activity.getTask().setCanAffectSystemUiFlags(false);
}
-
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
- + "transient-launch", mSyncId, activity);
}
/** @return whether `wc` is a descendent of a transient-hide window. */
@@ -459,9 +465,36 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return false;
}
+ /**
+ * This ensures that all changes for previously transient-hide containers are flagged such that
+ * they will report changes and be included in this transition.
+ */
+ void updateChangesForRestoreTransientHideTasks(Transition transientLaunchTransition) {
+ if (transientLaunchTransition.mTransientHideTasks == null) {
+ // Skip if the transient-launch transition has no transient-hide tasks
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Skipping update changes for restore transient hide tasks");
+ return;
+ }
+
+ // For each change, if it was previously transient-hidden, then we should force a flag to
+ // ensure that it is included in the next transition
+ for (int i = 0; i < mChanges.size(); i++) {
+ final WindowContainer container = mChanges.keyAt(i);
+ if (transientLaunchTransition.isInTransientHide(container)) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Force update transient hide task for restore %d: %s", mSyncId, container);
+ final ChangeInfo info = mChanges.valueAt(i);
+ info.mRestoringTransientHide = true;
+ }
+ }
+ }
+
/** Returns {@code true} if the task should keep visible if this is a transient transition. */
boolean isTransientVisible(@NonNull Task task) {
if (mTransientLaunches == null) return false;
+
+ // Check if all the transient-launch activities are occluded
int occludedCount = 0;
final int numTransient = mTransientLaunches.size();
for (int i = numTransient - 1; i >= 0; --i) {
@@ -469,23 +502,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (transientRoot == null) continue;
final WindowContainer<?> rootParent = transientRoot.getParent();
if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
- final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
- .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
- if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
- occludedCount++;
+ for (int j = rootParent.getChildCount() - 1; j >= 0; --j) {
+ final WindowContainer<?> sibling = rootParent.getChildAt(j);
+ if (sibling == transientRoot) break;
+ if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm
+ .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) {
+ occludedCount++;
+ break;
+ }
}
}
if (occludedCount == numTransient) {
- for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
- if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
- // Keep transient activity visible until transition finished, so it won't pause
- // with transient-hide tasks that may delay resuming the next top.
- return true;
- }
- }
// Let transient-hide activities pause before transition is finished.
return false;
}
+
+ // If this task is currently transient-hide, then keep it visible
return isInTransientHide(task);
}
@@ -547,17 +579,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (!ar.isVisible() || !ar.isVisibleRequested()) return;
if (mConfigAtEndActivities == null) {
mConfigAtEndActivities = new ArrayList<>();
- }
- if (mConfigAtEndActivities.contains(ar)) {
+ } else if (mConfigAtEndActivities.contains(ar)) {
return;
}
mConfigAtEndActivities.add(ar);
ar.pauseConfigurationDispatch();
- snapshotStartState(ar);
+ collect(ar);
mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
});
- snapshotStartState(wc);
- mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+ }
+
+ /** Set a transition to be a back gesture animation. */
+ void setBackGestureAnimation(@NonNull WindowContainer wc, boolean isTop) {
+ final ChangeInfo info = mChanges.get(wc);
+ if (info == null) return;
+ info.mFlags = info.mFlags | (isTop ? ChangeInfo.FLAG_BACK_GESTURE_ANIMATION
+ : ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION);
}
/** Set a transition to be a seamless-rotation. */
@@ -582,7 +619,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (top != null) {
mIsSeamlessRotation = true;
top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
+ "because seamless rotating", top.getName());
}
}
@@ -603,7 +640,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
/**
- * Only set flag to the parent tasks and activity itself.
+ * Sets the FLAG_TRANSIENT_LAUNCH flag to all changes associated with the given activity
+ * container and parent tasks.
*/
private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
@@ -699,7 +737,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return;
}
mState = STATE_STARTED;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
mSyncId);
applyReady();
@@ -719,7 +757,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Too late, transition already started playing, so don't collect.
return;
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// Snapshot before checking if this is a participant in case it has been re-parented.
snapshotStartState(getAnimatableParent(wc));
@@ -758,7 +796,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mChanges.put(curr, info);
if (isReadyGroup(curr)) {
mReadyTrackerOld.addGroup(curr);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
+ " Transition %d with root=%s", mSyncId, curr);
}
}
@@ -769,7 +807,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Only look at tasks, taskfragments, or activities
if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
if (!isInTransientHide(wc)) return;
- info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+ info.mFlags |= ChangeInfo.FLAG_TRANSIENT_HIDE;
}
private void recordDisplay(DisplayContent dc) {
@@ -831,8 +869,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Too late to collect. Don't check too-early here since `collect` will check that.
return;
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
- + " %s", mSyncId, wc);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Existence Changed in transition %d: %s", mSyncId, wc);
collect(wc);
mChanges.get(wc).mExistenceChanged = true;
}
@@ -997,7 +1035,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
} else {
ready = mReadyTrackerOld.allReady();
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Set transition ready=%b %d", ready, mSyncId);
boolean changed = mSyncEngine.setReady(mSyncId, ready);
if (changed && ready) {
@@ -1083,7 +1121,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// For config-at-end, the end-transform will be reset after the config is actually
// applied in the client (since the transform depends on config). The other properties
// remain here because shell might want to persistently override them.
- if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
+ if (target.asActivityRecord() == null
+ || (mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
resetSurfaceTransform(t, target, targetLeash);
}
}
@@ -1312,7 +1351,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Avoid commit visibility if entering pip or else we will get a sudden
// "flash" / surface going invisible for a split second.
if (commitVisibility) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" Commit activity becoming invisible: %s", ar);
final SnapshotController snapController = mController.mSnapshotController;
if (mTransientLaunches != null && !task.isVisibleRequested()
@@ -1326,7 +1365,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (lastSnapshotTimeNs < startTimeNs) {
snapController.mTaskSnapshotController.recordSnapshot(task);
} else {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" Skipping post-transition snapshot for task %d",
task.mTaskId);
}
@@ -1356,6 +1395,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.mAtm.setLastResumedActivityUncheckLocked(ar,
"transitionFinished");
}
+
+ // Prevent spurious background app switches.
+ if (ar.mDisplayContent.mFocusedApp == ar) {
+ mController.mAtm.stopAppSwitches();
+ }
}
continue;
}
@@ -1402,7 +1446,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final boolean isWallpaperVisibleAtEnd =
wt.isVisibleRequested() || mVisibleAtTransitionEndTokens.contains(wt);
if (isTargetInvisible || !isWallpaperVisibleAtEnd) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" Commit wallpaper becoming invisible: %s", wt);
wt.commitVisibility(false /* visible */);
}
@@ -1420,8 +1464,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (enterAutoPip) {
mController.mAtm.getTaskChangeNotificationController().notifyTaskStackChanged();
}
- // Prevent spurious background app switches.
- mController.mAtm.stopAppSwitches();
// The end of transient launch may not reorder task, so make sure to compute the latest
// task rank according to the current visibility.
mController.mAtm.mRootWindowContainer.rankTaskLayers();
@@ -1571,46 +1613,33 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) {
return;
}
- final SurfaceControl.Transaction t =
- mController.mAtm.mWindowManager.mTransactionFactory.get();
- for (int i = 0; i < mTargets.size(); ++i) {
- final WindowContainer target = mTargets.get(i).mContainer;
- if (target.getParent() == null || (mTargets.get(i).mFlags
- & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
- continue;
+ // Now resume the configuration dispatch, wait until the now resumed configs have been
+ // drawn, and then apply everything together. Any activities that are already in an
+ // active sync will remain on that sync instead of the new one.
+ int syncId = -1;
+ for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
+ final ActivityRecord target = mConfigAtEndActivities.get(i);
+ final SurfaceControl targetLeash = target.getSurfaceControl();
+ if (target.getSyncGroup() == null || target.getSyncGroup().isIgnoring(target)) {
+ if (syncId < 0) {
+ final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
+ (mSyncId, transaction) -> transaction.apply(),
+ "ConfigAtTransitEnd");
+ syncId = sg.mSyncId;
+ mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */);
+ mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST);
+ }
+ mSyncEngine.addToSyncSet(syncId, target);
}
- final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
// Reset surface state here (since it was skipped in buildFinishTransaction). Since
// we are resuming config to the "current" state, we have to calculate the matching
// surface state now (rather than snapshotting it at animation start).
- resetSurfaceTransform(t, target, targetLeash);
- }
-
- // Now we resume the configuration dispatch, wait until the now resumed configs have been
- // drawn, and then apply everything together.
- final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
- new BLASTSyncEngine.TransactionReadyListener() {
- @Override
- public void onTransactionReady(int mSyncId,
- SurfaceControl.Transaction transaction) {
- t.merge(transaction);
- t.apply();
- }
-
- @Override
- public void onTransactionCommitTimeout() {
- t.apply();
- }
- }, "ConfigAtTransitEnd");
- final int syncId = sg.mSyncId;
- mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */);
- mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST);
- for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
- final ActivityRecord ar = mConfigAtEndActivities.get(i);
- mSyncEngine.addToSyncSet(syncId, ar);
- ar.resumeConfigurationDispatch();
+ resetSurfaceTransform(target.getSyncTransaction(), target, targetLeash);
+ target.resumeConfigurationDispatch();
+ }
+ if (syncId >= 0) {
+ mSyncEngine.setReady(syncId);
}
- mSyncEngine.setReady(syncId);
}
@Nullable
@@ -1669,7 +1698,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
throw new IllegalStateException("Too late to abort. state=" + mState);
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Aborting Transition: %d", mSyncId);
mState = STATE_ABORT;
mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos();
mController.mTransitionTracer.logAbortedTransition(this);
@@ -1684,7 +1714,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (!(mState == STATE_COLLECTING || mState == STATE_STARTED)) {
return;
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
mSyncId);
mForcePlaying = true;
// backwards since conditions are removed.
@@ -1725,7 +1755,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
}
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) {
for (int i = list.size() - 1; i >= 0; --i) {
if (list.get(i).mContainer == wc) return true;
@@ -1742,7 +1771,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mController.useFullReadyTracking()) {
for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
mSyncId, mReadyTracker.mMet.get(i));
}
}
@@ -1921,7 +1950,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.dispatchLegacyAppTransitionStarting(participantDisplays,
mStatusBarTransitionDelay);
try {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos();
mLogger.mInfo = info;
@@ -1947,8 +1976,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// No player registered or it's not enabled, so just finish/apply immediately
if (!mIsPlayerEnabled) {
mLogger.mSendTimeNs = SystemClock.uptimeNanos();
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately"
- + " because player is disabled for transition #%d .", mSyncId);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Apply and finish immediately because player is disabled "
+ + "for transition #%d .", mSyncId);
}
postCleanupOnFailure();
}
@@ -2159,32 +2189,34 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
final Task task = onTopTasksEnd.get(i);
if (task.getDisplayId() != displayId) continue;
- if (!enableDisplayFocusInShellTransitions()
- || mOnTopDisplayStart == onTopDisplayEnd
- || displayId != onTopDisplayEnd.mDisplayId) {
- // If it didn't change since last report, don't report
- if (reportedOnTop == null) {
- if (mOnTopTasksStart.contains(task)) continue;
- } else if (reportedOnTop.contains(task)) {
- continue;
- }
- }
- // Need to report it.
- mParticipants.add(task);
- int changeIdx = mChanges.indexOfKey(task);
- if (changeIdx < 0) {
- mChanges.put(task, new ChangeInfo(task));
- changeIdx = mChanges.indexOfKey(task);
+ if (reportedOnTop == null) {
+ if (mOnTopTasksStart.contains(task)) continue;
+ } else if (reportedOnTop.contains(task)) {
+ continue;
}
- mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
+ addToTopChange(task);
}
// Swap in the latest on-top tasks.
mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd);
onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>();
onTopTasksEnd.clear();
+
+ if (enableDisplayFocusInShellTransitions()
+ && mOnTopDisplayStart != onTopDisplayEnd
+ && displayId == onTopDisplayEnd.mDisplayId) {
+ addToTopChange(onTopDisplayEnd);
+ }
}
}
+ private void addToTopChange(@NonNull WindowContainer wc) {
+ mParticipants.add(wc);
+ if (!mChanges.containsKey(wc)) {
+ mChanges.put(wc, new ChangeInfo(wc));
+ }
+ mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
+ }
+
private void postCleanupOnFailure() {
mController.mAtm.mH.post(() -> {
synchronized (mController.mAtm.mGlobalLock) {
@@ -2459,6 +2491,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
sb.append(" type=" + transitTypeToString(mType));
sb.append(" flags=0x" + Integer.toHexString(mFlags));
sb.append(" overrideAnimOptions=" + mOverrideOptions);
+ if (!mChanges.isEmpty()) {
+ sb.append(" c=[");
+ for (int i = 0; i < mChanges.size(); i++) {
+ sb.append("\n").append(" ").append(mChanges.valueAt(i).toString());
+ }
+ sb.append("\n]\n");
+ }
sb.append('}');
return sb.toString();
}
@@ -2555,12 +2594,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final ChangeInfo parentChange = changes.get(parent);
if (!parent.canCreateRemoteAnimationTarget()
|| parentChange == null || !parentChange.hasChanged()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
"parent can't be target " + parent);
return false;
}
if (isWallpaper(target)) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper");
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper");
return false;
}
@@ -2574,27 +2613,27 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer<?> sibling = parent.getChildAt(i);
if (target == sibling) continue;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
sibling);
final ChangeInfo siblingChange = changes.get(sibling);
if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
if (sibling.isVisibleRequested()) {
// Sibling is visible but not animating, so no promote.
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" SKIP: sibling is visible but not part of transition");
return false;
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" unrelated invisible sibling %s", sibling);
continue;
}
final int siblingMode = siblingChange.getTransitMode(sibling);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" sibling is a participant with mode %s",
TransitionInfo.modeToString(siblingMode));
if (reduceMode(mode) != reduceMode(siblingMode)) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" SKIP: common mode mismatch. was %s",
TransitionInfo.modeToString(mode));
return false;
@@ -2624,10 +2663,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
for (int i = targets.mArray.size() - 1; i >= 0; --i) {
final ChangeInfo targetChange = targets.mArray.valueAt(i);
final WindowContainer<?> target = targetChange.mContainer;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target);
final WindowContainer<?> parent = target.getParent();
if (parent == lastNonPromotableParent) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" SKIP: its sibling was rejected");
continue;
}
@@ -2636,16 +2675,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
continue;
}
if (reportIfNotTop(target)) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" keep as target %s", target);
+ } else if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
+ // config-at-end activities do not match the end-state, so they should be treated
+ // as independent.
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ " keep as cfg-at-end target %s", target);
} else {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" remove from targets %s", target);
targets.remove(i);
}
final ChangeInfo parentChange = changes.get(parent);
if (targets.mArray.indexOfValue(parentChange) < 0) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" CAN PROMOTE: promoting to parent %s", parent);
// The parent has lower depth, so it will be checked in the later iteration.
i++;
@@ -2656,9 +2700,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
} else {
parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
}
- if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
- parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
- }
}
}
@@ -2670,7 +2711,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
@NonNull
static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
ArrayMap<WindowContainer, ChangeInfo> changes) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Start calculating TransitionInfo based on participants: %s", participants);
// Add all valid participants to the target container.
@@ -2678,7 +2719,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
for (int i = participants.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = participants.valueAt(i);
if (!wc.isAttached()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" Rejecting as detached: %s", wc);
continue;
}
@@ -2688,13 +2729,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final ChangeInfo changeInfo = changes.get(wc);
// Reject no-ops
if (!changeInfo.hasChanged()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" Rejecting as no-op: %s vis: %b", wc, wc.isVisibleRequested());
continue;
}
targets.add(changeInfo);
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s",
targets.mArray);
// Combine the targets from bottom to top if possible.
tryPromote(targets, changes);
@@ -2702,7 +2743,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
populateParentChanges(targets, changes);
final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList);
return targetList;
}
@@ -2750,14 +2791,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
} else {
intermediates.add(parentChange);
}
- // for config-at-end, we want to promote the flag based on the end-state even
- // if the activity was reparented because it operates after the animation. So,
- // check that here since the promote code skips reparents.
- if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0
- && targetChange.mContainer.asActivityRecord() != null
- && targetChange.mContainer.getParent() == p) {
- parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
- }
foundParentInTargets = true;
break;
} else if (reportIfNotTop(p) && !skipIntermediateReports) {
@@ -3413,8 +3446,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
* seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
*/
private static final int FLAG_SEAMLESS_ROTATION = 1;
+ /**
+ * Identifies the associated WindowContainer as a transient-launch task or activity.
+ */
private static final int FLAG_TRANSIENT_LAUNCH = 2;
- private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
+ /**
+ * Identifies the associated WindowContainer as a transient-hide task or activity.
+ */
+ private static final int FLAG_TRANSIENT_HIDE = 4;
/** This container explicitly requested no-animation (usually Activity level). */
private static final int FLAG_CHANGE_NO_ANIMATION = 0x8;
@@ -3430,18 +3469,32 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
/** Whether this change contains config-at-end members. */
private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40;
+ /**
+ * Whether this change is forced participant transition because it is current top target
+ * of predictive back animation.
+ */
+ private static final int FLAG_BACK_GESTURE_ANIMATION = 0x80;
+
+ /**
+ * Whether this change is forced participant transition because it is previous target of
+ * predictive back animation
+ */
+ private static final int FLAG_BELOW_BACK_GESTURE_ANIMATION = 0x100;
+
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
FLAG_SEAMLESS_ROTATION,
FLAG_TRANSIENT_LAUNCH,
- FLAG_ABOVE_TRANSIENT_LAUNCH,
+ FLAG_TRANSIENT_HIDE,
FLAG_CHANGE_NO_ANIMATION,
FLAG_CHANGE_YES_ANIMATION,
FLAG_CHANGE_MOVED_TO_TOP,
- FLAG_CHANGE_CONFIG_AT_END
+ FLAG_CHANGE_CONFIG_AT_END,
+ FLAG_BACK_GESTURE_ANIMATION,
+ FLAG_BELOW_BACK_GESTURE_ANIMATION
})
@Retention(RetentionPolicy.SOURCE)
- @interface Flag {}
+ @interface ChangeInfoFlag {}
@NonNull final WindowContainer mContainer;
/**
@@ -3460,6 +3513,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// State tracking
boolean mExistenceChanged = false;
+ // This state indicates that we are restoring transient order as a part of an
+ // end-transition. Because the visibility for transient hide containers has not actually
+ // changed, we need to ensure that hasChanged() still reports the relevant changes
+ boolean mRestoringTransientHide = false;
// before change state
boolean mVisible;
int mWindowingMode;
@@ -3470,7 +3527,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
@ActivityInfo.Config int mKnownConfigChanges;
/** Extra information about this change. */
- @Flag int mFlags = FLAG_NONE;
+ @ChangeInfoFlag int mFlags = FLAG_NONE;
/** Snapshot surface and luma, if relevant. */
SurfaceControl mSnapshot;
@@ -3505,18 +3562,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
@Override
public String toString() {
- return mContainer.toString();
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("ChangeInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" container=").append(mContainer);
+ sb.append(" flags=0x").append(Integer.toHexString(mFlags));
+ sb.append('}');
+ return sb.toString();
}
boolean hasChanged() {
+ final boolean currVisible = mContainer.isVisibleRequested();
// the task including transient launch must promote to root task
- if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
- || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+ if (currVisible && ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
+ || (mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0)
+ || (mFlags & ChangeInfo.FLAG_BACK_GESTURE_ANIMATION) != 0
+ || (mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
return true;
}
// If it's invisible and hasn't changed visibility, always return false since even if
// something changed, it wouldn't be a visible change.
- final boolean currVisible = mContainer.isVisibleRequested();
if (currVisible == mVisible && !mVisible) return false;
return currVisible != mVisible
|| mKnownConfigChanges != 0
@@ -3526,16 +3591,28 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|| !mContainer.getBounds().equals(mAbsoluteBounds)
|| mRotation != mContainer.getWindowConfiguration().getRotation()
|| mDisplayId != getDisplayId(mContainer)
- || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
+ || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0
+ // If we are restoring transient-hide containers, then we should consider them
+ // important for the transition as well (their requested visibilities would not
+ // have changed for the checks below to consider it).
+ || mRestoringTransientHide;
}
@TransitionInfo.TransitionMode
int getTransitMode(@NonNull WindowContainer wc) {
- if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+ if ((mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0) {
return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
}
+ if ((mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
+ return TRANSIT_TO_FRONT;
+ }
final boolean nowVisible = wc.isVisibleRequested();
if (nowVisible == mVisible) {
+ if (mRestoringTransientHide) {
+ // The requested visibility has not changed for transient-hide containers, but
+ // we are restoring them so we should considering them moving to front again
+ return TRANSIT_TO_FRONT;
+ }
return TRANSIT_CHANGE;
}
if (mExistenceChanged) {
@@ -3557,6 +3634,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) {
flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
}
+ final TaskDisplayArea tda = wc.asTaskDisplayArea();
+ if (tda != null) {
+ flags |= TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA;
+ }
final Task task = wc.asTask();
if (task != null) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
@@ -3565,7 +3646,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
&& topActivity.mStartingData.hasImeSurface()) {
flags |= FLAG_WILL_IME_SHOWN;
}
- if (topActivity.mLaunchTaskBehind) {
+ if (topActivity.mLaunchTaskBehind
+ && !topActivity.isAnimating(PARENTS, ANIMATION_TYPE_PREDICT_BACK)) {
Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
flags |= FLAG_TASK_LAUNCHING_BEHIND;
}
@@ -3805,7 +3887,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
mConditions.add(condition);
condition.mTracker = this;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d",
condition, mTransition.mSyncId);
condition.startTracking();
}
@@ -3827,7 +3909,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
condition.mMet = true;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d"
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d"
+ " left)", condition, mTransition.mSyncId, mConditions.size());
mMet.add(condition);
mTransition.applyReady();
@@ -3896,8 +3978,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
while (current != null) {
if (isReadyGroup(current)) {
mReadyGroups.put(current, ready);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
- + " %b. group=%s from %s", ready, current, wc);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Setting Ready-group to %b. group=%s from %s", ready, current, wc);
break;
}
current = current.getParent();
@@ -3906,14 +3988,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
/** Marks this as ready regardless of individual groups. */
void setAllReady() {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
mUsed = true;
mReadyOverride = true;
}
/** @return true if all tracked subtrees are ready. */
boolean allReady() {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" allReady query: used=%b " + "override=%b defer=%d states=[%s]", mUsed,
mReadyOverride, mDeferReadyDepth, groupsToString());
// If the readiness has never been touched, mUsed will be false. We never want to
@@ -4051,7 +4133,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
wc.toString(), bounds.toString());
Rect cropBounds = new Rect(bounds);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6b3a954ae060..f3c03cbfb3b4 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,6 +37,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -53,7 +54,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.server.FgThread;
import com.android.window.flags.Flags;
@@ -303,7 +304,8 @@ class TransitionController {
+ " possible.");
}
Transition transit = new Transition(type, flags, this, mSyncEngine);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Creating Transition: %s", transit);
moveToCollecting(transit);
return transit;
}
@@ -324,21 +326,21 @@ class TransitionController {
final long timeoutMs =
transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
mCollectingTransition.startCollecting(timeoutMs);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
- mCollectingTransition);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Start collecting in Transition: %s", mCollectingTransition);
}
void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@Nullable WindowProcessController playerProc) {
if (!mTransitionPlayers.isEmpty()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition "
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition "
+ "player %s over %d other players", player.asBinder(),
mTransitionPlayers.size());
// flush currently running transitions so that the new player doesn't get
// intermediate state
flushRunningTransitions();
} else {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition "
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition "
+ "player %s ", player.asBinder());
}
mTransitionPlayers.add(new TransitionPlayerRecord(player, playerProc));
@@ -351,18 +353,18 @@ class TransitionController {
if (mTransitionPlayers.get(idx).mPlayer.asBinder() == player.asBinder()) break;
}
if (idx < 0) {
- ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Attempt to unregister "
+ ProtoLog.w(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Attempt to unregister "
+ "transition player %s but it isn't registered", player.asBinder());
return;
}
final boolean needsFlush = idx == (mTransitionPlayers.size() - 1);
final TransitionPlayerRecord record = mTransitionPlayers.remove(idx);
if (needsFlush) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering active "
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering active "
+ "transition player %s at index=%d leaving %d in stack", player.asBinder(),
idx, mTransitionPlayers.size());
} else {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering transition "
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering transition "
+ "player %s at index=%d leaving %d in stack", player.asBinder(), idx,
mTransitionPlayers.size());
}
@@ -470,6 +472,16 @@ class TransitionController {
return false;
}
+ /** Returns {@code true} if the `wc` is a target of a playing transition. */
+ boolean isPlayingTarget(@NonNull WindowContainer<?> wc) {
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (Transition.containsChangeFor(wc, mPlayingTransitions.get(i).mTargets)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Returns {@code true} if the finishing transition contains `wc`. */
boolean inFinishingTransition(WindowContainer<?> wc) {
return mFinishingTransition != null && mFinishingTransition.isInTransition(wc);
@@ -513,6 +525,23 @@ class TransitionController {
return false;
}
+ /**
+ * @return A pair of the transition and restore-behind target for the given {@param container}.
+ * @param container An ancestor of a transient-launch activity
+ */
+ @Nullable
+ Pair<Transition, Task> getTransientLaunchTransitionAndTarget(
+ @NonNull WindowContainer container) {
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ final Transition transition = mPlayingTransitions.get(i);
+ final Task restoreBehindTask = transition.getTransientLaunchRestoreTarget(container);
+ if (restoreBehindTask != null) {
+ return new Pair<>(transition, restoreBehindTask);
+ }
+ }
+ return null;
+ }
+
/** Returns {@code true} if the display contains a transient-launch transition. */
boolean hasTransientLaunch(@NonNull DisplayContent dc) {
if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch()
@@ -629,8 +658,8 @@ class TransitionController {
}
// Always allow WindowState to assign layers since it won't affect transition.
return wc.asWindowState() != null || (!isPlaying()
- // Don't assign task while collecting.
- && !(wc.asTask() != null && isCollecting()));
+ // Don't assign task or display area layers while collecting.
+ && !((wc.asTask() != null || wc.asDisplayArea() != null) && isCollecting()));
}
@WindowConfiguration.WindowingMode
@@ -741,8 +770,9 @@ class TransitionController {
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange) {
if (mIsWaitingForDisplayEnabled) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Disabling player for transition"
- + " #%d because display isn't enabled yet", transition.getSyncId());
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Disabling player for transition #%d because display isn't enabled yet",
+ transition.getSyncId());
transition.mIsPlayerEnabled = false;
transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos();
mAtm.mH.post(() -> mAtm.mWindowOrganizerController.startTransition(
@@ -758,22 +788,33 @@ class TransitionController {
return transition;
}
try {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
ActivityManager.RunningTaskInfo startTaskInfo = null;
- ActivityManager.RunningTaskInfo pipTaskInfo = null;
+ TransitionRequestInfo.PipChange pipChange = null;
if (startTask != null) {
startTaskInfo = startTask.getTaskInfo();
}
// set the pip task in the request if provided
if (transition.getPipActivity() != null) {
- pipTaskInfo = transition.getPipActivity().getTask().getTaskInfo();
+ ActivityManager.RunningTaskInfo pipTaskInfo =
+ transition.getPipActivity().getTask().getTaskInfo();
+ ActivityRecord pipActivity = transition.getPipActivity();
+ if (pipActivity.getTaskFragment() != null
+ && pipActivity.getTaskFragment() != pipActivity.getTask()) {
+ // If the PiP activity is in a TF different from its task, this could be
+ // AE-to-PiP case, so PipChange will have the TF token cached separately.
+ pipChange = new TransitionRequestInfo.PipChange(pipActivity.getTaskFragment()
+ .mRemoteToken.toWindowContainerToken(), pipTaskInfo);
+ } else {
+ pipChange = new TransitionRequestInfo.PipChange(pipTaskInfo);
+ }
transition.setPipActivity(null);
}
final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
- startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
+ startTaskInfo, pipChange, remoteTransition, displayChange,
transition.getFlags(), transition.getSyncId());
transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
@@ -935,7 +976,7 @@ class TransitionController {
Slog.e(TAG, "Trying to finish a non-playing transition " + record);
return;
}
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
mPlayingTransitions.remove(record);
if (!inTransition()) {
// reset track-count now since shell-side is idle.
@@ -1056,8 +1097,8 @@ class TransitionController {
// If it's a legacy sync, then it needs to wait until there is no collecting transition.
if (queued.mTransition == null) return;
if (!canStartCollectingNow(queued.mTransition)) return;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from collecting"
- + " to waiting.", mCollectingTransition.getSyncId());
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "Moving #%d from collecting to waiting.", mCollectingTransition.getSyncId());
mWaitingTransitions.add(mCollectingTransition);
mCollectingTransition = null;
} else if (mSyncEngine.hasActiveSync()) {
@@ -1220,8 +1261,8 @@ class TransitionController {
// Didn't overlap with anything, so give it its own track
track = mTrackCount;
if (track > 0) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Playing #%d in parallel on "
- + "track #%d", transition.getSyncId(), track);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Playing #%d in parallel on track #%d", transition.getSyncId(), track);
}
}
transition.mAnimationTrack = track;
@@ -1230,8 +1271,8 @@ class TransitionController {
if (sync && mTrackCount > 1) {
// If there are >1 tracks, mark as sync so that all tracks finish.
info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.",
- transition.getSyncId());
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Marking #%d animation as SYNC.", transition.getSyncId());
}
}
@@ -1410,7 +1451,7 @@ class TransitionController {
/** Returns {@code true} if it started collecting, {@code false} if it was queued. */
private void queueTransition(Transition transit, OnStartCollect onStartCollect) {
mQueuedTransitions.add(new QueuedTransition(transit, onStartCollect));
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
"Queueing transition: %s", transit);
}
@@ -1426,7 +1467,7 @@ class TransitionController {
// Check if we can run in parallel here.
if (canStartCollectingNow(transit)) {
// start running in parallel.
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+ " collecting to waiting.", mCollectingTransition.getSyncId());
mWaitingTransitions.add(mCollectingTransition);
mCollectingTransition = null;
@@ -1465,7 +1506,7 @@ class TransitionController {
// Check if we can run in parallel here.
if (canStartCollectingNow(null /* transit */)) {
// create and collect in parallel.
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+ " collecting to waiting.", mCollectingTransition.getSyncId());
mWaitingTransitions.add(mCollectingTransition);
mCollectingTransition = null;
@@ -1489,7 +1530,7 @@ class TransitionController {
// Just add to queue since we already have a queue.
mQueuedTransitions.add(new QueuedTransition(syncGroup,
(deferred) -> applySync.accept(true /* deferred */)));
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
"Queueing legacy sync-set: %s", syncGroup.mSyncId);
return;
}
@@ -1684,9 +1725,10 @@ class TransitionController {
}
void logOnSend() {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " startWCT=%s", mStartWCT);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s",
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ " startWCT=%s", mStartWCT);
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s",
mInfo.toString(" " /* prefix */));
}
@@ -1709,7 +1751,7 @@ class TransitionController {
}
void logOnFinish() {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnFinishLog());
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnFinishLog());
}
}
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 85a118db36eb..edd99243c3ef 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -196,10 +196,11 @@ class TransparentPolicy {
// We evaluate the case when the policy should not be applied.
private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) {
- if (opaqueActivity == null || opaqueActivity.isEmbedded()) {
+ if (opaqueActivity == null || opaqueActivity.isEmbedded()
+ || !opaqueActivity.areBoundsLetterboxed()) {
// We skip letterboxing if the translucent activity doesn't have any
// opaque activities beneath or the activity below is embedded which
- // never has letterbox.
+ // never has letterbox or the activity is not letterboxed at all.
return true;
}
final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index c0dc4247a7c2..242883612124 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -21,7 +21,7 @@ import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TPL;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -156,7 +156,7 @@ public class TrustedPresentationListenerController {
Listeners mRegisteredListeners = new Listeners();
- private InputWindowHandle[] mLastWindowHandles;
+ private Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> mLastWindowHandles;
private void startHandlerThreadIfNeeded() {
synchronized (mHandlerThreadLock) {
@@ -222,10 +222,10 @@ public class TrustedPresentationListenerController {
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- mHandler.post(() -> computeTpl(windowHandles));
+ mHandler.post(() -> computeTpl(new Pair<>(windowHandles, displayInfos)));
}
};
- mLastWindowHandles = mWindowInfosListener.register().first;
+ mLastWindowHandles = mWindowInfosListener.register();
}
private void unregisterWindowInfosListener() {
@@ -238,28 +238,52 @@ public class TrustedPresentationListenerController {
mLastWindowHandles = null;
}
- private void computeTpl(InputWindowHandle[] windowHandles) {
+ private void computeTpl(
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> windowHandles) {
mLastWindowHandles = windowHandles;
- if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+ if (mLastWindowHandles == null || mLastWindowHandles.first.length == 0
|| mRegisteredListeners.isEmpty()) {
return;
}
Rect tmpRect = new Rect();
+ RectF tmpRectF = new RectF();
+ Rect tmpLogicalDisplaySize = new Rect();
Matrix tmpInverseMatrix = new Matrix();
float[] tmpMatrix = new float[9];
Region coveredRegionsAbove = new Region();
long currTimeMs = System.currentTimeMillis();
- ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+ ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
new ArrayMap<>();
- for (var windowHandle : mLastWindowHandles) {
+ for (var windowHandle : mLastWindowHandles.first) {
if (!windowHandle.canOccludePresentation) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
- tmpRect.set(windowHandle.frame);
+ var displayFound = false;
+ tmpRectF.set(windowHandle.frame);
+ for (var displayHandle : mLastWindowHandles.second) {
+ if (displayHandle.mDisplayId == windowHandle.displayId) {
+ // Transform the window frame into display logical space and then
+ // crop by the logical display size
+ displayHandle.mTransform.mapRect(tmpRectF);
+ tmpRectF.round(tmpRect);
+ tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
+ displayHandle.mLogicalSize.getHeight());
+ tmpRect.intersect(tmpLogicalDisplaySize);
+ displayFound = true;
+ break;
+ }
+ }
+
+ if (!displayFound) {
+ ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
+ windowHandle.displayId);
+ continue;
+ }
+
var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
if (listeners != null) {
Region region = new Region();
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index fffe692a39dd..c3e85b1f8662 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -15,7 +15,7 @@
*/
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 06010bb1642e..7565b5d9fd4e 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -23,7 +23,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 89ad5640ef83..2c926fcae26a 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -19,8 +19,8 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 2342de3676de..49c8559c02a8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1c03ba571923..aa60f939f9aa 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -32,16 +32,17 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
+import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.isActivityTransitOld;
@@ -173,6 +174,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
+ /**
+ * The combined excluded insets types (combined mExcludeInsetsTypes and the
+ * mMergedExcludeInsetsTypes from its parent)
+ */
+ protected @InsetsType int mMergedExcludeInsetsTypes = 0;
+ private @InsetsType int mExcludeInsetsTypes = 0;
+
@Nullable
private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
@@ -555,6 +563,49 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return mControllableInsetProvider;
}
+ /**
+ * Sets the excludeInsetsTypes of this window and updates the mMergedExcludeInsetsTypes of
+ * all child nodes in the hierarchy.
+ *
+ * @param excludeInsetsTypes the excluded {@link InsetsType} that should be set on this
+ * WindowContainer
+ */
+ void setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+ if (excludeInsetsTypes == mExcludeInsetsTypes) {
+ return;
+ }
+ mExcludeInsetsTypes = excludeInsetsTypes;
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+ mParent != null ? mParent.mMergedExcludeInsetsTypes : 0);
+ }
+
+ private void mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+ @InsetsType int excludeInsetsTypesFromParent) {
+ final ArraySet<WindowState> changedWindows = new ArraySet<>();
+ updateMergedExcludeInsetsTypes(excludeInsetsTypesFromParent, changedWindows);
+ if (getDisplayContent() != null) {
+ getDisplayContent().getInsetsStateController().notifyInsetsChanged(changedWindows);
+ }
+ }
+
+ private void updateMergedExcludeInsetsTypes(
+ @InsetsType int excludeInsetsTypesFromParent, ArraySet<WindowState> changedWindows) {
+ final int newMergedExcludeInsetsTypes = mExcludeInsetsTypes | excludeInsetsTypesFromParent;
+ if (newMergedExcludeInsetsTypes == mMergedExcludeInsetsTypes) {
+ return;
+ }
+ mMergedExcludeInsetsTypes = newMergedExcludeInsetsTypes;
+
+ final WindowState win = asWindowState();
+ if (win != null) {
+ changedWindows.add(win);
+ }
+ // Apply to all children
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowContainer<?> child = mChildren.get(i);
+ child.updateMergedExcludeInsetsTypes(mMergedExcludeInsetsTypes, changedWindows);
+ }
+ }
@Override
final protected WindowContainer getParent() {
@@ -653,6 +704,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
super.onParentChanged(newParent, oldParent);
if (mParent == null) {
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(0);
return;
}
@@ -667,6 +719,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// new parent.
reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
}
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(mParent.mMergedExcludeInsetsTypes);
// Either way we need to ask the parent to assign us a Z-order.
mParent.assignChildLayers();
@@ -1064,6 +1117,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
void onDisplayChanged(DisplayContent dc) {
if (mDisplayContent != null && mDisplayContent != dc) {
+ if (asWindowState() == null) {
+ mTransitionController.collect(this);
+ }
// Cancel any change transition queued-up for this container on the old display when
// this container is moved from the old display.
mDisplayContent.mClosingChangingContainers.remove(this);
@@ -1620,30 +1676,36 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}.
* @param requestingContainer the container which orientation request has changed. Mostly used
* to ensure it gets correct configuration.
+ * @return the resolved override orientation of this window container.
*/
- void setOrientation(@ScreenOrientation int orientation,
+ @ScreenOrientation
+ int setOrientation(@ScreenOrientation int orientation,
@Nullable WindowContainer requestingContainer) {
if (getOverrideOrientation() == orientation) {
- return;
+ return orientation;
}
-
setOverrideOrientation(orientation);
final WindowContainer parent = getParent();
- if (parent != null) {
- if (getConfiguration().orientation != getRequestedConfigurationOrientation()
- // Update configuration directly only if the change won't be dispatched from
- // ancestor. This prevents from computing intermediate configuration when the
- // parent also needs to be updated from the ancestor. E.g. the app requests
- // portrait but the task is still in landscape. While updating from display,
- // the task can be updated to portrait first so the configuration can be
- // computed in a consistent environment.
- && (inMultiWindowMode()
+ if (parent == null) {
+ return orientation;
+ }
+ // The derived class can return a result that is different from the given orientation.
+ final int resolvedOrientation = getOverrideOrientation();
+ if (getConfiguration().orientation != getRequestedConfigurationOrientation(
+ false /* forDisplay */, resolvedOrientation)
+ // Update configuration directly only if the change won't be dispatched from
+ // ancestor. This prevents from computing intermediate configuration when the
+ // parent also needs to be updated from the ancestor. E.g. the app requests
+ // portrait but the task is still in landscape. While updating from display,
+ // the task can be updated to portrait first so the configuration can be
+ // computed in a consistent environment.
+ && (inMultiWindowMode()
|| !handlesOrientationChangeFromDescendant(orientation))) {
- // Resolve the requested orientation.
- onConfigurationChanged(parent.getConfiguration());
- }
- onDescendantOrientationChanged(requestingContainer);
+ // Resolve the requested orientation.
+ onConfigurationChanged(parent.getConfiguration());
}
+ onDescendantOrientationChanged(requestingContainer);
+ return resolvedOrientation;
}
@ScreenOrientation
@@ -2065,7 +2127,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
- * For all tasks at or below this container call the callback.
+ * Calls the given {@param callback} for all tasks in depth-first top-down z-order at or below
+ * this container.
*
* @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
* stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
@@ -3158,8 +3221,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
&& isChangingAppTransition();
- // Delaying animation start isn't compatible with remote animations at all.
- if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
+ if (controller != null) {
// Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.
boolean showBackdrop = false;
// Optionally set backdrop color if App explicitly provides it through
@@ -3582,20 +3644,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return getAnimatingContainer(PARENTS, ANIMATION_TYPE_ALL);
}
- /**
- * @see SurfaceAnimator#startDelayingAnimationStart
- */
- void startDelayingAnimationStart() {
- mSurfaceAnimator.startDelayingAnimationStart();
- }
-
- /**
- * @see SurfaceAnimator#endDelayingAnimationStart
- */
- void endDelayingAnimationStart() {
- mSurfaceAnimator.endDelayingAnimationStart();
- }
-
@Override
public int getSurfaceWidth() {
return mSurfaceControl.getWidth();
@@ -3685,7 +3733,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
&& !mTransitionController.useShellTransitionsRotation()) {
if (deltaRotation != Surface.ROTATION_0) {
updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
- getPendingTransaction().setFixedTransformHint(mSurfaceControl,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), mSurfaceControl,
getWindowConfiguration().getDisplayRotation());
} else if (deltaRotation != mLastDeltaRotation) {
t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index 80f3c44267d7..ae477e1e22f0 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -19,7 +19,7 @@ package com.android.server.wm;
import static android.view.SurfaceControl.METADATA_OWNER_UID;
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 87e120c9a15d..6d0da1fd36f5 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -21,8 +21,8 @@ import static android.view.Display.isSuspendedState;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.window.WindowProviderService.isWindowProviderService;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -167,6 +167,22 @@ class WindowContextListenerController {
return true;
}
+ boolean assertCallerCanReparentListener(@NonNull IBinder clientToken,
+ boolean callerCanManageAppTokens, int callingUid, int displayId) {
+ if (!assertCallerCanModifyListener(clientToken, callerCanManageAppTokens, callingUid)) {
+ return false;
+ }
+
+ final WindowContainer<?> container = getContainer(clientToken);
+ if (container != null && container.getDisplayContent() != null
+ && container.getDisplayContent().mDisplayId == displayId) {
+ ProtoLog.i(WM_DEBUG_ADD_REMOVE,
+ "The listener has already been attached to the same display id");
+ return false;
+ }
+ return true;
+ }
+
boolean hasListener(IBinder clientToken) {
return mListeners.containsKey(clientToken);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index e0f24d8bf447..9a5c8dffc0fc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -22,10 +22,12 @@ import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURE_EXCLUSION_
import android.provider.AndroidDeviceConfig;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -34,11 +36,34 @@ import java.util.concurrent.Executor;
*/
final class WindowManagerConstants {
- /** The orientation of activity will be always "unspecified" except for game apps. */
+ /**
+ * The orientation of activity will be always "unspecified" except for game apps.
+ * <p>Possible values:
+ * <ul>
+ * <li>false: applies to no apps (default)</li>
+ * <li>true: applies to all apps</li>
+ * </ul>
+ */
private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST =
"ignore_activity_orientation_request";
/**
+ * The orientation of activity will be always "unspecified" except for game apps.
+ * <p>Possible values:
+ * <ul>
+ * <li>none: applies to no apps (default)</li>
+ * <li>all: applies to all apps ({@see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST})</li>
+ * <li>large: applies to all apps but only on large screens</li>
+ * </ul>
+ */
+ private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS =
+ "ignore_activity_orientation_request_screens";
+
+ /** The packages that ignore {@link #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST}. */
+ private static final String KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST =
+ "opt_out_ignore_activity_orientation_request_list";
+
+ /**
* The minimum duration between gesture exclusion logging for a given window in
* milliseconds.
*
@@ -63,7 +88,11 @@ final class WindowManagerConstants {
boolean mSystemGestureExcludedByPreQStickyImmersive;
/** @see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST */
- boolean mIgnoreActivityOrientationRequest;
+ boolean mIgnoreActivityOrientationRequestLargeScreen;
+ boolean mIgnoreActivityOrientationRequestSmallScreen;
+
+ /** @see #KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST */
+ private ArraySet<String> mOptOutIgnoreActivityOrientationRequestPackages;
private final WindowManagerGlobalLock mGlobalLock;
private final Runnable mUpdateSystemGestureExclusionCallback;
@@ -97,6 +126,7 @@ final class WindowManagerConstants {
updateSystemGestureExclusionLimitDp();
updateSystemGestureExcludedByPreQStickyImmersive();
updateIgnoreActivityOrientationRequest();
+ updateOptOutIgnoreActivityOrientationRequestList();
}
private void onAndroidPropertiesChanged(DeviceConfig.Properties properties) {
@@ -136,8 +166,12 @@ final class WindowManagerConstants {
updateSystemGestureExclusionLogDebounceMillis();
break;
case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST:
+ case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS:
updateIgnoreActivityOrientationRequest();
break;
+ case KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST:
+ updateOptOutIgnoreActivityOrientationRequestList();
+ break;
default:
break;
}
@@ -164,9 +198,34 @@ final class WindowManagerConstants {
}
private void updateIgnoreActivityOrientationRequest() {
- mIgnoreActivityOrientationRequest = mDeviceConfig.getBoolean(
+ boolean allScreens = mDeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER,
KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false);
+ String whichScreens = mDeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS);
+ allScreens |= ("all".equalsIgnoreCase(whichScreens));
+ boolean largeScreens = allScreens || ("large".equalsIgnoreCase(whichScreens));
+ mIgnoreActivityOrientationRequestSmallScreen = allScreens;
+ mIgnoreActivityOrientationRequestLargeScreen = largeScreens;
+ }
+
+ private void updateOptOutIgnoreActivityOrientationRequestList() {
+ final String packageList = mDeviceConfig.getString(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST, "");
+ if (packageList.isEmpty()) {
+ mOptOutIgnoreActivityOrientationRequestPackages = null;
+ return;
+ }
+ mOptOutIgnoreActivityOrientationRequestPackages = new ArraySet<>();
+ mOptOutIgnoreActivityOrientationRequestPackages.addAll(
+ Arrays.asList(packageList.split(",")));
+ }
+
+ boolean isPackageOptOutIgnoreActivityOrientationRequest(String packageName) {
+ return mOptOutIgnoreActivityOrientationRequestPackages != null
+ && mOptOutIgnoreActivityOrientationRequestPackages.contains(packageName);
}
void dump(PrintWriter pw) {
@@ -178,8 +237,13 @@ final class WindowManagerConstants {
pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
pw.print(" "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
- pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
- pw.print("="); pw.println(mIgnoreActivityOrientationRequest);
+ pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS);
+ pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "all"
+ : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "none");
+ if (mOptOutIgnoreActivityOrientationRequestPackages != null) {
+ pw.print(" "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST);
+ pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages);
+ }
pw.println();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 82d39a39d188..c77b1d9a7bcf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -46,7 +46,6 @@ import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
-import android.view.WindowInfo;
import android.view.WindowManager.DisplayImePolicy;
import android.view.inputmethod.ImeTracker;
import android.window.ScreenCapture;
@@ -158,26 +157,8 @@ public abstract class WindowManagerInternal {
* accessibility changed.
*/
public interface WindowsForAccessibilityCallback {
-
- /**
- * Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
- * false.
- *
- * @param forceSend Send the windows for accessibility even if they haven't changed.
- * @param topFocusedDisplayId The display Id which has the top focused window.
- * @param topFocusedWindowToken The window token of top focused window.
- * @param windows The windows for accessibility.
- */
- void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
- IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows);
-
/**
- * Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
- * true.
- * TODO(b/322444245): Remove screenSize parameter by getting it from
- * DisplayManager#getDisplay(int).getRealSize() on the a11y side.
+ * Called when the windows for accessibility changed.
*
* @param forceSend Send the windows for accessibility even if they haven't changed.
* @param topFocusedDisplayId The display Id which has the top focused window.
@@ -859,6 +840,18 @@ public abstract class WindowManagerInternal {
public abstract boolean isHomeSupportedOnDisplay(int displayId);
/**
+ * Sets whether the relevant display content ignores fixed orientation, aspect ratio
+ * and resizability of apps.
+ *
+ * @param displayUniqueId The unique ID of the display. Note that the display may not yet be
+ * created, but whenever it is, this property will be applied.
+ * @param displayType The type of the display, e.g. {@link Display#TYPE_VIRTUAL}.
+ * @param enabled Whether app is universal resizable on this display.
+ */
+ public abstract void setIgnoreActivitySizeRestrictionsOnDisplay(
+ @NonNull String displayUniqueId, int displayType, boolean enabled);
+
+ /**
* Removes any settings relevant to the given display.
*
* <p>This may be used when a property is set for a display unique ID before the display
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1a3f54ae9a18..6238d4bd8fb8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -69,6 +69,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -101,18 +102,19 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.window.WindowProviderService.isWindowProviderService;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED;
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.LockGuard.INDEX_WINDOW;
@@ -157,9 +159,9 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.INPUT_METHOD_W
import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
-import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import android.Manifest;
import android.Manifest.permission;
@@ -335,7 +337,7 @@ import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.protolog.LegacyProtoLogImpl;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -356,6 +358,7 @@ import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -1028,12 +1031,12 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
- final boolean disableSecureWindows;
+ boolean disableSecureWindows;
try {
disableSecureWindows = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.DISABLE_SECURE_WINDOWS, 0) != 0;
} catch (Settings.SettingNotFoundException e) {
- return;
+ disableSecureWindows = false;
}
if (mDisableSecureWindows == disableSecureWindows) {
return;
@@ -2115,7 +2118,7 @@ public class WindowManagerService extends IWindowManager.Stub
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Removing %s from %s", win, token);
// Window will already be removed from token before this post clean-up method is called.
if (token.isEmpty() && !token.mPersistOnEmpty) {
- token.removeImmediately();
+ token.removeIfPossible();
}
if (win.mActivityRecord != null) {
@@ -3036,8 +3039,8 @@ public class WindowManagerService extends IWindowManager.Stub
mWindowContextListenerController.unregisterWindowContainerListener(clientToken);
- final WindowToken token = wc.asWindowToken();
- if (token != null && token.isFromClient()) {
+ final WindowToken token = wc != null ? wc.asWindowToken() : null;
+ if (token != null && token.isFromClient() && token.getDisplayContent() != null) {
removeWindowToken(token.token, token.getDisplayContent().getDisplayId());
}
}
@@ -3046,6 +3049,81 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
+ public boolean reparentWindowContextToDisplayArea(
+ @NonNull IApplicationThread appThread, @NonNull IBinder clientToken, int displayId) {
+ if (!Flags.reparentWindowTokenApi()) {
+ return false;
+ }
+ Objects.requireNonNull(appThread);
+ Objects.requireNonNull(clientToken);
+ final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
+ "reparentWindowContextToDisplayArea", false /* printLog */);
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final WindowProcessController wpc = mAtmService.getProcessController(appThread);
+ if (wpc == null) {
+ ProtoLog.w(WM_ERROR, "reparentWindowContextToDisplayArea: calling from"
+ + " non-existing process pid=%d uid=%d", callingPid, callingUid);
+ return false;
+ }
+ final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
+ if (dc == null) {
+ ProtoLog.w(WM_ERROR, "reparentWindowContextToDisplayArea: trying to attach"
+ + " to a non-existing display:%d", displayId);
+ return false;
+ }
+
+ if (!mWindowContextListenerController.assertCallerCanReparentListener(clientToken,
+ callerCanManageAppTokens, callingUid, displayId)) {
+ return false;
+ }
+ final WindowContainer<?> container = mWindowContextListenerController.getContainer(
+ clientToken);
+
+ final WindowToken token = container != null ? container.asWindowToken() : null;
+ if (token != null && token.isFromClient()) {
+ ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Reparenting from dc to displayId=%d",
+ displayId);
+ // Reparent the window created for this window context.
+ dc.reParentWindowToken(token);
+ hideUntilNextDraw(token);
+ // This makes sure there is a traversal scheduled that will eventually report
+ // the window resize to the client.
+ dc.setLayoutNeeded();
+ requestTraversal();
+ return true;
+ }
+
+ final int type = mWindowContextListenerController.getWindowType(clientToken);
+ final Bundle options = mWindowContextListenerController.getOptions(clientToken);
+ // No window yet, switch listening DA.
+ final DisplayArea<?> da = dc.findAreaForWindowType(type, options,
+ callerCanManageAppTokens, false /* roundedCornerOverlay */);
+ mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
+ da, type, options, true /* shouldDispatchConfigWhenRegistering */);
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ private void hideUntilNextDraw(@NonNull WindowToken token) {
+ final WindowState topChild = token.getTopChild();
+ if (topChild != null) {
+ mTransactionFactory.get().hide(token.mSurfaceControl).apply();
+ topChild.applyWithNextDraw(t -> {
+ if (token.mSurfaceControl != null) {
+ t.show(token.mSurfaceControl);
+ }
+ });
+ }
+ }
+
/** Returns {@code true} if this binder is a registered window token. */
@Override
public boolean isWindowToken(IBinder binder) {
@@ -3427,9 +3505,11 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires CONTROL_KEYGUARD permission");
}
synchronized (mGlobalLock) {
+ final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
if (!dreamHandlesConfirmKeys()
- && getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw()) {
- mAtmService.mTaskSupervisor.wakeUp("leaveDream");
+ && defaultDisplayContent.getDisplayPolicy().isShowingDreamLw()) {
+ mAtmService.mTaskSupervisor.wakeUp(
+ defaultDisplayContent.getDisplayId(), "leaveDream");
}
mPolicy.dismissKeyguardLw(callback, message);
}
@@ -3896,8 +3976,8 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mGlobalLock) {
mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Notified TransitionController "
- + "that the display is ready.");
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Notified TransitionController that the display is ready.");
}
}
@@ -4672,21 +4752,26 @@ public class WindowManagerService extends IWindowManager.Stub
@EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
- public void updateDisplayWindowRequestedVisibleTypes(
- int displayId, @InsetsType int requestedVisibleTypes) {
+ public void updateDisplayWindowRequestedVisibleTypes(int displayId,
+ @InsetsType int visibleTypes, @InsetsType int mask,
+ @Nullable ImeTracker.Token statsToken) {
updateDisplayWindowRequestedVisibleTypes_enforcePermission();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
return;
}
- dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
+ dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(visibleTypes, mask);
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
- // IME provider. Check if we have to create a new request here
+ // IME provider. Check if we have to create a new request here, if null.
dc.getInsetsStateController().onRequestedVisibleTypesChanged(
- dc.mRemoteInsetsControlTarget, null /* statsToken */);
+ dc.mRemoteInsetsControlTarget, statsToken);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -6816,30 +6901,36 @@ public class WindowManagerService extends IWindowManager.Stub
* @param logLevel Determines the amount of data to be written to the Protobuf.
*/
void dumpDebugLocked(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
- mPolicy.dumpDebug(proto, POLICY);
- mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
- final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
- if (topFocusedDisplayContent.mCurrentFocus != null) {
- topFocusedDisplayContent.mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
- }
- if (topFocusedDisplayContent.mFocusedApp != null) {
- topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
- }
- final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
- if (imeWindow != null) {
- imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
- }
- proto.write(DISPLAY_FROZEN, mDisplayFrozen);
- proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
- proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
+ try {
+ mPolicy.dumpDebug(proto, POLICY);
+ mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
+ final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
+ if (topFocusedDisplayContent.mCurrentFocus != null) {
+ topFocusedDisplayContent.mCurrentFocus
+ .writeIdentifierToProto(proto, FOCUSED_WINDOW);
+ }
+ if (topFocusedDisplayContent.mFocusedApp != null) {
+ topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+ }
+ final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+ if (imeWindow != null) {
+ imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
+ }
+ proto.write(DISPLAY_FROZEN, mDisplayFrozen);
+ proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
+ proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
- // This is always true for now since we still update the window frames at the server side.
- // Once we move the window layout to the client side, this can be false when we are waiting
- // for the frames.
- proto.write(WINDOW_FRAMES_VALID, true);
+ // This is always true for now since we still update the window frames at the server
+ // side. Once we move the window layout to the client side, this can be false when we
+ // are waiting for the frames.
+ proto.write(WINDOW_FRAMES_VALID, true);
- // Write the BackNavigationController's state into the protocol buffer
- mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+ // Write the BackNavigationController's state into the protocol buffer
+ mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
}
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
@@ -7694,7 +7785,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ "not exist: %d", displayId);
return false;
}
- return displayContent.supportsSystemDecorations();
+ return displayContent.isSystemDecorationsSupported();
}
}
@@ -8385,6 +8476,20 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void setIgnoreActivitySizeRestrictionsOnDisplay(@NonNull String displayUniqueId,
+ int displayType, boolean enabled) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ mDisplayWindowSettings.setIgnoreActivitySizeRestrictionsOnDisplayLocked(
+ displayUniqueId, displayType, enabled);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public void clearDisplaySettings(String displayUniqueId, int displayType) {
final long origId = Binder.clearCallingIdentity();
try {
@@ -8997,16 +9102,19 @@ public class WindowManagerService extends IWindowManager.Stub
clearPointerDownOutsideFocusRunnable();
+ final InputTarget focusedInputTarget = mFocusedInputTarget;
if (shouldDelayTouchOutside(t)) {
- mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mPointerDownOutsideFocusRunnable =
+ () -> handlePointerDownOutsideFocus(t, focusedInputTarget);
mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
} else if (!fromHandler) {
// Still post the runnable to handler thread in case there is already a runnable
// in execution, but still waiting to hold the wm lock.
- mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mPointerDownOutsideFocusRunnable =
+ () -> handlePointerDownOutsideFocus(t, focusedInputTarget);
mH.post(mPointerDownOutsideFocusRunnable);
} else {
- handlePointerDownOutsideFocus(t);
+ handlePointerDownOutsideFocus(t, focusedInputTarget);
}
}
@@ -9016,7 +9124,9 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean isInputTargetNotFocused =
mFocusedInputTarget != t && mFocusedInputTarget != null;
- if (!isInputTargetNotFocused) {
+ final boolean isTouchOnFocusedDisplay = mFocusedInputTarget != null
+ && t.getDisplayId() == mFocusedInputTarget.getDisplayId();
+ if (!(isInputTargetNotFocused && isTouchOnFocusedDisplay)) {
return false;
}
@@ -9036,8 +9146,15 @@ public class WindowManagerService extends IWindowManager.Stub
return shouldDelayTouchForEmbeddedActivity || shouldDelayTouchForFreeform;
}
- private void handlePointerDownOutsideFocus(InputTarget t) {
+ private void handlePointerDownOutsideFocus(InputTarget t, InputTarget focusedInputTarget) {
synchronized (mGlobalLock) {
+ if (mFocusedInputTarget != focusedInputTarget) {
+ // Skip if the mFocusedInputTarget is already changed. This is possible if the
+ // pointer-down-outside-focus event is delayed to be handled.
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
+ "Skip onPointerDownOutsideFocusLocked due to input target changed %s", t);
+ return;
+ }
if (mPointerDownOutsideFocusRunnable != null
&& mH.hasCallbacks(mPointerDownOutsideFocusRunnable)) {
// Skip if there's another pending pointer-down-outside-focus event.
@@ -9202,6 +9319,25 @@ public class WindowManagerService extends IWindowManager.Stub
+ "' because it isn't a trusted overlay");
return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
}
+
+ // You need OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission to be able
+ // to set INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS.
+ if (overridePowerKeyBehaviorInFocusedWindow()
+ && (inputFeatures
+ & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
+ != 0) {
+ final int powerPermissionResult =
+ mContext.checkPermission(
+ permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+ callingPid,
+ callingUid);
+ if (powerPermissionResult != PackageManager.PERMISSION_GRANTED) {
+ throw new IllegalArgumentException(
+ "Cannot use INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS from" + windowName
+ + " because it doesn't have the"
+ + " OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission");
+ }
+ }
return inputFeatures;
}
@@ -10180,6 +10316,22 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * Returns whether the display with the given ID is trusted.
+ */
+ @Override
+ public boolean isDisplayTrusted(int displayId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ DisplayContent dc = mRoot.getDisplayContent(displayId);
+ return dc != null && dc.isTrusted();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@RequiresPermission(ACCESS_SURFACE_FLINGER)
@Override
public boolean replaceContentOnDisplay(int displayId, SurfaceControl sc) {
@@ -10203,6 +10355,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * Resets the spatial ordering of recents for testing purposes.
+ */
+ void resetFreezeRecentTaskListReordering() {
+ if (!checkCallingPermission(permission.MANAGE_ACTIVITY_TASKS,
+ "resetFreezeRecentTaskListReordering()")) {
+ throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+ }
+ mAtmService.getRecentTasks().resetFreezeTaskListReorderingOnTimeout();
+ }
+
@Override
public void registerTrustedPresentationListener(IBinder window,
ITrustedPresentationListener listener,
@@ -10273,7 +10436,7 @@ public class WindowManagerService extends IWindowManager.Stub
mH.post(() -> {
Toast.makeText(mContext, Looper.getMainLooper(),
mContext.getString(R.string.screen_not_shared_sensitive_content),
- Toast.LENGTH_SHORT)
+ Toast.LENGTH_LONG)
.show();
});
// If blocked due to notification protection (null window token) log protection applied
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 06d8c370b914..44f5f51eb623 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.os.Build.IS_USER;
import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
@@ -30,6 +33,7 @@ import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_RE
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import android.app.WindowConfiguration;
import android.content.res.Resources.NotFoundException;
import android.graphics.Color;
import android.graphics.Point;
@@ -157,6 +161,12 @@ public class WindowManagerShellCommand extends ShellCommand {
return runReset(pw);
case "disable-blur":
return runSetBlurDisabled(pw);
+ case "reset-freeze-recent-tasks":
+ return runResetFreezeRecentTaskListReordering(pw);
+ case "set-display-windowing-mode":
+ return runSetDisplayWindowingMode(pw);
+ case "get-display-windowing-mode":
+ return runGetDisplayWindowingMode(pw);
case "shell":
return runWmShellCommand(pw);
default:
@@ -267,6 +277,11 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runResetFreezeRecentTaskListReordering(PrintWriter pw) throws RemoteException {
+ mInternal.resetFreezeRecentTaskListReordering();
+ return 0;
+ }
+
private void printInitialDisplayDensity(PrintWriter pw , int displayId) {
try {
final int initialDensity = mInterface.getInitialDisplayDensity(displayId);
@@ -1038,6 +1053,25 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetCameraCompatAspectRatio(PrintWriter pw) throws RemoteException {
+ final float aspectRatio;
+ try {
+ String arg = getNextArgRequired();
+ aspectRatio = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: aspect ratio should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mAppCompatConfiguration.setCameraCompatAspectRatio(aspectRatio);
+ }
+ return 0;
+ }
+
private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -1129,6 +1163,13 @@ public class WindowManagerShellCommand extends ShellCommand {
runSetBooleanFlag(pw,
mAppCompatConfiguration::setCameraCompatRefreshCycleThroughStopEnabled);
break;
+ case "--cameraCompatAspectRatio":
+ runSetCameraCompatAspectRatio(pw);
+ break;
+ case "--isCameraCompatFreeformWindowingTreatmentEnabled":
+ runSetBooleanFlag(pw, mAppCompatConfiguration
+ ::setIsCameraCompatFreeformWindowingTreatmentEnabled);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1220,6 +1261,13 @@ public class WindowManagerShellCommand extends ShellCommand {
mAppCompatConfiguration
.resetCameraCompatRefreshCycleThroughStopEnabled();
break;
+ case "cameraCompatAspectRatio":
+ mAppCompatConfiguration.resetCameraCompatAspectRatio();
+ break;
+ case "isCameraCompatFreeformWindowingTreatmentEnabled":
+ mAppCompatConfiguration
+ .resetIsCameraCompatFreeformWindowingTreatmentEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1330,6 +1378,8 @@ public class WindowManagerShellCommand extends ShellCommand {
mAppCompatConfiguration.resetUserAppAspectRatioFullscreenEnabled();
mAppCompatConfiguration.resetCameraCompatRefreshEnabled();
mAppCompatConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
+ mAppCompatConfiguration.resetCameraCompatAspectRatio();
+ mAppCompatConfiguration.resetIsCameraCompatFreeformWindowingTreatmentEnabled();
}
}
@@ -1404,10 +1454,43 @@ public class WindowManagerShellCommand extends ShellCommand {
+ mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled());
pw.println("Is the fullscreen option in user aspect ratio settings enabled: "
+ mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled());
+ pw.println("Default aspect ratio for camera compat freeform: "
+ + mAppCompatConfiguration.getCameraCompatAspectRatio());
+ pw.println("Is camera compatibility freeform treatment enabled for all apps: "
+ + mAppCompatConfiguration.isCameraCompatFreeformWindowingTreatmentEnabled());
}
return 0;
}
+ private int runSetDisplayWindowingMode(PrintWriter pw) throws RemoteException {
+ int displayId = Display.DEFAULT_DISPLAY;
+ String arg = getNextArgRequired();
+ if ("-d".equals(arg)) {
+ displayId = Integer.parseInt(getNextArgRequired());
+ arg = getNextArgRequired();
+ }
+
+ final int windowingMode = Integer.parseInt(arg);
+ mInterface.setWindowingMode(displayId, windowingMode);
+
+ return 0;
+ }
+
+ private int runGetDisplayWindowingMode(PrintWriter pw) throws RemoteException {
+ int displayId = Display.DEFAULT_DISPLAY;
+ final String arg = getNextArg();
+ if ("-d".equals(arg)) {
+ displayId = Integer.parseInt(getNextArgRequired());
+ }
+
+ final int windowingMode = mInterface.getWindowingMode(displayId);
+ pw.println("display windowing mode="
+ + WindowConfiguration.windowingModeToString(windowingMode) + " for displayId="
+ + displayId);
+
+ return 0;
+ }
+
private int runWmShellCommand(PrintWriter pw) {
String arg = getNextArg();
@@ -1486,6 +1569,9 @@ public class WindowManagerShellCommand extends ShellCommand {
// set-multi-window-config
runResetMultiWindowConfig();
+ // set-display-windowing-mode
+ mInternal.setWindowingMode(displayId, WINDOWING_MODE_UNDEFINED);
+
pw.println("Reset all settings for displayId=" + displayId);
return 0;
}
@@ -1526,6 +1612,14 @@ public class WindowManagerShellCommand extends ShellCommand {
printLetterboxHelp(pw);
printMultiWindowConfigHelp(pw);
+ pw.println(" reset-freeze-recent-tasks");
+ pw.println(" Resets the spatial ordering of the recent tasks list");
+ pw.println(" set-display-windowing-mode [-d DISPLAY_ID] [mode_id]");
+ pw.println(" As mode_id, use " + WINDOWING_MODE_UNDEFINED + " for undefined, "
+ + WINDOWING_MODE_FREEFORM + " for freeform, " + WINDOWING_MODE_FULLSCREEN + " for"
+ + " fullscreen");
+ pw.println(" get-display-windowing-mode [-d DISPLAY_ID]");
+
pw.println(" reset [-d DISPLAY_ID]");
pw.println(" Reset all override settings.");
if (!IS_USER) {
@@ -1619,6 +1713,14 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" Whether activity \"refresh\" in camera compatibility treatment should");
pw.println(" happen using the \"stopped -> resumed\" cycle rather than");
pw.println(" \"paused -> resumed\" cycle.");
+ pw.println(" --cameraCompatAspectRatio aspectRatio");
+ pw.println(" Aspect ratio of letterbox for fixed-orientation camera apps, during");
+ pw.println(" freeform camera compat mode. If aspectRatio <= "
+ + AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ pw.println(" it will be ignored.");
+ pw.println(" --isCameraCompatFreeformWindowingTreatmentEnabled [true|1|false|0]");
+ pw.println(" Whether camera compat treatment is enabled in freeform mode for all");
+ pw.println(" eligible apps.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
@@ -1627,7 +1729,9 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
pw.println(" |persistentPositionMultiplierForHorizontalReachability");
pw.println(" |persistentPositionMultiplierForVerticalReachability");
- pw.println(" |defaultPositionMultiplierForVerticalReachability]");
+ pw.println(" |defaultPositionMultiplierForVerticalReachability");
+ pw.println(" |cameraCompatAspectRatio");
+ pw.println(" |isCameraCompatFreeformWindowingTreatmentEnabled]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
pw.println(" If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4a223f32887d..66921ff3adeb 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -51,6 +51,7 @@ import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
@@ -67,13 +68,15 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
@@ -108,6 +111,7 @@ import android.os.RemoteException;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
@@ -120,6 +124,7 @@ import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
+import android.window.KeyguardState;
import android.window.RemoteTransition;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
@@ -130,7 +135,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
@@ -571,7 +576,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// {@link #applySyncTransaction} with Shell transition.
// We still want to apply and merge the transaction to the active sync
// because {@code shouldApplyIndependently} is {@code false}.
- ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ ProtoLog.w(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"TaskFragmentTransaction changes are not collected in transition"
+ " because there is an ongoing sync for"
+ " applySyncTransaction().");
@@ -799,7 +804,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
} finally {
if (deferTransitionReady) {
- chain.mTransition.continueTransitionReady();
+ if (chain.mTransition.isCollecting()) {
+ chain.mTransition.continueTransitionReady();
+ } else {
+ Slog.wtf(TAG, "Too late, transition : " + chain.mTransition.getSyncId()
+ + " state: " + chain.mTransition.getState() + " is not collecting");
+ }
}
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
if (deferResume) {
@@ -1248,6 +1258,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
caller, errorCallbackToken, organizer);
break;
}
+ case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: {
+ effects |= applyKeyguardState(hop);
+ break;
+ }
case HIERARCHY_OP_TYPE_PENDING_INTENT: {
final Bundle launchOpts = hop.getLaunchOptions();
ActivityOptions activityOptions = launchOpts != null
@@ -1321,11 +1335,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
- Task pipTask = container.asTask();
- if (pipTask == null) {
+ TaskFragment pipTaskFragment = container.asTaskFragment();
+ if (pipTaskFragment == null) {
break;
}
- ActivityRecord pipActivity = pipTask.getActivity(
+ ActivityRecord pipActivity = pipTaskFragment.getActivity(
(activity) -> activity.pictureInPictureArgs != null);
if (pipActivity.isState(RESUMED)) {
@@ -1359,16 +1373,56 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
- if (!chain.isFinishing()) break;
+ if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ // Only allow restoring transient order when finishing a transition
+ if (!chain.isFinishing()) break;
+ }
+ // Validate the container
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
- if (container == null) break;
+ if (container == null) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: invalid container");
+ break;
+ }
final Task thisTask = container.asActivityRecord() != null
? container.asActivityRecord().getTask() : container.asTask();
- if (thisTask == null) break;
- final Task restoreAt = chain.mTransition.getTransientLaunchRestoreTarget(container);
- if (restoreAt == null) break;
+ if (thisTask == null) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: invalid task");
+ break;
+ }
+
+ // Find the task to restore behind
+ final Pair<Transition, Task> transientRestore =
+ mTransitionController.getTransientLaunchTransitionAndTarget(container);
+ if (transientRestore == null) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: no restore task");
+ break;
+ }
+ final Transition transientLaunchTransition = transientRestore.first;
+ final Task restoreAt = transientRestore.second;
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: restoring behind task=%d", restoreAt.mTaskId);
+
+ // Restore the position of the given container behind the target task
final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
+
+ if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ // Because we are in a transient launch transition, the requested visibility of
+ // tasks does not actually change for the transient-hide tasks, but we do want
+ // the restoration of these transient-hide tasks to top to be a part of this
+ // finish transition
+ final Transition collectingTransition =
+ mTransitionController.getCollectingTransition();
+ if (collectingTransition != null) {
+ collectingTransition.updateChangesForRestoreTransientHideTasks(
+ transientLaunchTransition);
+ }
+ }
+
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
}
case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: {
@@ -1439,12 +1493,33 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
task.setTrimmableFromRecents(hop.isTrimmableFromRecents());
break;
}
+ case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ final Task task = container != null ? container.asTask() : null;
+ if (task == null || !task.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ task.setLaunchAdjacentDisabled(hop.isLaunchAdjacentDisabled());
+ break;
+ }
case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: {
if (mService.mBackNavigationController.restoreBackNavigation()) {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes());
+ break;
+ }
}
return effects;
}
@@ -1502,8 +1577,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final IBinder callerActivityToken = operation.getActivityToken();
final Intent activityIntent = operation.getActivityIntent();
final Bundle activityOptions = operation.getBundle();
+ final SafeActivityOptions safeOptions =
+ SafeActivityOptions.fromBundle(activityOptions, caller.mPid, caller.mUid);
final int result = waitAsyncStart(() -> mService.getActivityStartController()
- .startActivityInTaskFragment(taskFragment, activityIntent, activityOptions,
+ .startActivityInTaskFragment(taskFragment, activityIntent, safeOptions,
callerActivityToken, caller.mUid, caller.mPid,
errorCallbackToken));
if (!isStartResultSuccessful(result)) {
@@ -1773,6 +1850,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects;
}
+ private int applyKeyguardState(@NonNull WindowContainerTransaction.HierarchyOp hop) {
+ int effects = TRANSACT_EFFECTS_LIFECYCLE;
+
+ final KeyguardState keyguardState = hop.getKeyguardState();
+ if (keyguardState != null) {
+ boolean keyguardShowing = keyguardState.getKeyguardShowing();
+ boolean aodShowing = keyguardState.getAodShowing();
+ mService.setLockScreenShown(keyguardShowing, aodShowing);
+ }
+ return effects;
+ }
+
/**
* Executes the provided {@code runnable} after the {@code transition}. If the
* {@code transition} is {@code null}, the {@code runnable} is executed immediately.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 75c99af6a603..7f0c33657144 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -27,7 +27,7 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
@@ -121,6 +121,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
*/
private static final int MAX_NUM_PERCEPTIBLE_FREEFORM =
SystemProperties.getInt("persist.wm.max_num_perceptible_freeform", 1);
+ /**
+ * If the visible area percentage of a resumed freeform task is greater than or equal to this
+ * ratio, its process will have a higher priority.
+ */
+ private static final int PERCEPTIBLE_FREEFORM_VISIBLE_RATIO = 90;
private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200;
private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
@@ -1234,6 +1239,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
boolean hasResumedFreeform = false;
int minTaskLayer = Integer.MAX_VALUE;
int stateFlags = 0;
+ int nonOccludedRatio = 0;
final boolean wasResumed = hasResumedActivity();
final boolean wasAnyVisible = (mActivityStateFlags
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
@@ -1261,6 +1267,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
stateFlags |= ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN;
} else if (windowingMode == WINDOWING_MODE_FREEFORM) {
hasResumedFreeform = true;
+ nonOccludedRatio =
+ Math.max(task.mNonOccludedFreeformAreaRatio, nonOccludedRatio);
}
}
if (minTaskLayer > 0) {
@@ -1298,7 +1306,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
&& com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
// Exclude task layer 1 because it is already the top most.
&& minTaskLayer > 1) {
- if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+ if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM
+ || nonOccludedRatio >= PERCEPTIBLE_FREEFORM_VISIBLE_RATIO) {
stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
} else {
stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
@@ -1543,6 +1552,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
unregisterConfigurationListeners();
mConfigActivityRecord = activityRecord;
activityRecord.registerConfigurationChangeListener(this);
+ // If the process hasn't attached, make sure that prepareConfigurationForLaunchingActivity
+ // will use the newer configuration sequence.
+ if (mThread == null) {
+ mHasPendingConfigurationChange = true;
+ }
}
private void unregisterActivityConfigurationListener() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 014d045a34ba..eb30514006f1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -100,19 +100,19 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static com.android.input.flags.Flags.removeInputChannelFromWindowstate;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DIMMER;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_RESIZE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_INSETS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
@@ -157,6 +157,7 @@ import static com.android.server.wm.WindowStateProto.ANIMATING_EXIT;
import static com.android.server.wm.WindowStateProto.ANIMATOR;
import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
import static com.android.server.wm.WindowStateProto.DESTROYING;
+import static com.android.server.wm.WindowStateProto.DIM_BOUNDS;
import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
@@ -181,8 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
-import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -213,6 +212,7 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.WorkSource;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
@@ -258,6 +258,7 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.utils.RegionUtils;
import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -792,6 +793,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
private final List<DrawHandler> mDrawHandlers = new ArrayList<>();
+ /**
+ * Indicates whether inset animations are currently running within the Window.
+ * This value is used by (@link com.android.server.wm.RefreshRatePolicy.java)
+ * to omit setting a frame rate on the WindowState. Insets Animation is unique in that
+ * sense that an app might drive an insets animation for a Window owned by a different
+ * app (such as IME). In that case, we need the app that drives the insets animation
+ * to be able to vote for high refresh rate from VRI.
+ */
+ private boolean mInsetsAnimationRunning;
+
private final Consumer<SurfaceControl.Transaction> mSeamlessRotationFinishedConsumer = t -> {
finishSeamlessRotation(t);
updateSurfacePosition(t);
@@ -1186,9 +1197,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
}
- if (secureWindowState()) {
- getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
- }
+ getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
// All apps should be considered as occluding when computing TrustedPresentation Thresholds.
final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow;
getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation);
@@ -1378,6 +1387,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// should be updated after the new given insets are sent to window manager.
return;
}
+ if (!mRelayoutCalled) {
+ // The window was not laid out yet. The source frame should be updated after the window
+ // is laid out.
+ return;
+ }
final SparseArray<InsetsSourceProvider> providers = getInsetsSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
providers.valueAt(i).updateSourceFrame(winFrame);
@@ -2322,7 +2336,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mStartingData != null && mStartingData.mAssociatedTask == null
&& mTempConfiguration.windowConfiguration.getRotation()
== selfConfiguration.windowConfiguration.getRotation()
- && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
+ && !RegionUtils.sizeEquals(
+ mTempConfiguration.windowConfiguration.getBounds(), getBounds())) {
mStartingData.mResizedFromTransfer = true;
// Lock the starting window to task, so it won't resize from transfer anymore.
mActivityRecord.associateStartingWindowWithTaskIfNeeded();
@@ -2772,6 +2787,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
if (mTmpRect.isEmpty()) {
+ // TODO(b/371182877) If the app does not draw under cutout region, the touchable region
+ // should not include cutout regions (if scrolling from letterbox feature is not desired
+ // for this region).
final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
if (transformedBounds != null) {
// Task is in the same orientation as display, so the rotated bounds should be
@@ -2854,12 +2872,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (allowTheaterMode && canTurnScreenOn
&& (mWmService.mAtmService.isDreaming()
- || !mWmService.mPowerManager.isInteractive())) {
+ || !mWmService.mPowerManager.isInteractive(getDisplayId()))) {
if (DEBUG_VISIBILITY || DEBUG_POWER) {
Slog.v(TAG, "Relayout window turning screen on: " + this);
}
mWmService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG");
+ PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG",
+ getDisplayId());
}
if (mActivityRecord != null) {
@@ -2978,15 +2997,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
}
- @Override
- void resolveOverrideConfiguration(Configuration newParentConfig) {
- super.resolveOverrideConfiguration(newParentConfig);
- if (mActivityRecord != null) {
- // Let the activity decide whether to apply the size override.
- return;
- }
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- resolvedConfig.seq = newParentConfig.seq;
+ void applySizeOverride(Configuration newParentConfig, Configuration resolvedConfig) {
applySizeOverrideIfNeeded(
getDisplayContent(),
mSession.mProcess.mInfo,
@@ -3297,6 +3308,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
android.os.Process.killProcess(mSession.mPid);
}
}
+
+ // Because the client is notified to be invisible, it should no longer be considered as
+ // drawn state. This prevent the app from showing incomplete content if the app is
+ // requested to be visible in a short time (e.g. before activity stopped).
+ if (Flags.resetDrawStateOnClientInvisible() && !clientVisible && mActivityRecord != null
+ && mWinAnimator.mDrawState == HAS_DRAWN) {
+ mWinAnimator.resetDrawState();
+ // Make sure the app can report drawn if it becomes visible again.
+ forceReportingResized();
+ }
}
void onStartFreezingScreen() {
@@ -3360,8 +3381,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (cleanupOnResume) {
requestUpdateWallpaperIfNeeded();
}
- mDestroying = false;
- destroyedSomething = true;
+ if (!mHasSurface) {
+ mDestroying = false;
+ destroyedSomething = true;
+ }
// Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another
// traversal in case we are waiting on this window to start the transition.
@@ -3414,7 +3437,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
&& mAttrs.type != TYPE_PRIVATE_PRESENTATION
&& !(mAttrs.type == TYPE_PRESENTATION && isOnVirtualDisplay())
) {
- mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
+ mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid,
+ mAttrs.type, shown);
}
}
@@ -4098,6 +4122,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES);
}
}
+ if (getDimController() != null) {
+ final Rect dimBounds = getDimController().getDimBounds();
+ if (dimBounds != null) {
+ dimBounds.dumpDebug(proto, DIM_BOUNDS);
+ }
+ }
proto.end(token);
}
@@ -5277,12 +5307,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (voteChanged) {
getPendingTransaction()
.setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
- mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
- mFrameRateVote.mSelectionStrategy);
- }
-
+ mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS)
+ .setFrameRateSelectionStrategy(mSurfaceControl,
+ mFrameRateVote.mSelectionStrategy);
}
}
@@ -6157,21 +6184,28 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
void setSecureLocked(boolean isSecure) {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName());
- if (secureWindowState()) {
- if (mSurfaceControl == null) {
- return;
- }
- getPendingTransaction().setSecure(mSurfaceControl, isSecure);
- } else {
- if (mWinAnimator.mSurfaceControl == null) {
- return;
- }
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
- isSecure);
+ if (mSurfaceControl == null) {
+ return;
}
+ getPendingTransaction().setSecure(mSurfaceControl, isSecure);
if (mDisplayContent != null) {
mDisplayContent.refreshImeSecureFlag(getSyncTransaction());
}
mWmService.scheduleAnimationLocked();
}
+
+ void notifyInsetsAnimationRunningStateChanged(boolean running) {
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER,
+ TextUtils.formatSimple("%s: notifyInsetsAnimationRunningStateChanged(%s)",
+ getName(),
+ Boolean.toString(running)));
+ }
+ mInsetsAnimationRunning = running;
+ mWmService.scheduleAnimationLocked();
+ }
+
+ boolean isInsetsAnimationRunning() {
+ return mInsetsAnimationRunning;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 82fa9d4db534..0154d95d888d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -27,12 +27,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DRAW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DRAW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
@@ -49,7 +49,6 @@ import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import android.content.Context;
@@ -310,12 +309,6 @@ class WindowStateAnimator {
int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = w.mAttrs;
- if (!secureWindowState()) {
- if (w.isSecureLocked()) {
- flags |= SurfaceControl.SECURE;
- }
- }
-
if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
flags |= SurfaceControl.SKIP_SCREENSHOT;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 67bd5cb833ae..cca73c574951 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -18,10 +18,10 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowContainerChildProto.WINDOW_TOKEN;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -49,6 +49,7 @@ import android.window.WindowContext;
import com.android.internal.protolog.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -90,6 +91,9 @@ class WindowToken extends WindowContainer<WindowState> {
// Is key dispatching paused for this token?
boolean paused = false;
+ /** Whether this container should be removed when it no longer animates. */
+ boolean mIsExiting;
+
/** The owner has {@link android.Manifest.permission#MANAGE_APP_TOKENS} */
final boolean mOwnerCanManageAppTokens;
@@ -276,6 +280,28 @@ class WindowToken extends WindowContainer<WindowState> {
}
}
+ @Override
+ void removeIfPossible() {
+ if (mTransitionController.isPlayingTarget(this)) {
+ // Defer removing this container until the transition is finished. So the removal can
+ // execute after the finish transaction (see Transition#buildFinishTransaction) which
+ // may reparent it to original parent.
+ mIsExiting = true;
+ return;
+ }
+ mIsExiting = false;
+ removeAllWindowsIfPossible();
+ removeImmediately();
+ }
+
+ @Override
+ boolean handleCompleteDeferredRemoval() {
+ if (mIsExiting) {
+ removeIfPossible();
+ }
+ return super.handleCompleteDeferredRemoval();
+ }
+
/**
* @return The scale for applications running in compatibility mode. Multiply the size in the
* application by this scale will be the size in the screen.
@@ -361,7 +387,15 @@ class WindowToken extends WindowContainer<WindowState> {
@Override
void onDisplayChanged(DisplayContent dc) {
- dc.reParentWindowToken(this);
+ if (!Flags.reparentWindowTokenApi()) {
+ dc.reParentWindowToken(this);
+ } else {
+ // This check is needed to break recursion, as DisplayContent#reparentWindowToken also
+ // triggers a WindowToken#onDisplayChanged.
+ if (dc.getWindowToken(token) == null) {
+ dc.reParentWindowToken(this);
+ }
+ }
// TODO(b/36740756): One day this should perhaps be hooked
// up with goodToGo, so we don't move a window
@@ -589,6 +623,12 @@ class WindowToken extends WindowContainer<WindowState> {
final int rotation = getRelativeDisplayRotation();
if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
+ if (ActivityTaskManagerService.isPip2ExperimentEnabled() && asActivityRecord() != null
+ && mTransitionController.getWindowingModeAtStart(
+ asActivityRecord()) == WINDOWING_MODE_PINNED) {
+ // PiP handles fixed rotation animation in Shell, so do not create the rotation leash.
+ return null;
+ }
final SurfaceControl leash = makeSurface().setContainerLayer()
.setParent(getParentSurfaceControl())
@@ -598,7 +638,7 @@ class WindowToken extends WindowContainer<WindowState> {
.build();
t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
t.reparent(getSurfaceControl(), leash);
- getPendingTransaction().setFixedTransformHint(leash,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), leash,
getWindowConfiguration().getDisplayRotation());
mFixedRotationTransformLeash = leash;
updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
@@ -641,6 +681,15 @@ class WindowToken extends WindowContainer<WindowState> {
getResolvedOverrideConfiguration().updateFrom(
mFixedRotationTransformState.mRotatedOverrideConfiguration);
}
+ if (asActivityRecord() == null) {
+ // Let ActivityRecord override the config if there is one. Otherwise, override here.
+ // Resolve WindowToken's configuration by the latest window.
+ final WindowState win = getTopChild();
+ if (win != null) {
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ win.applySizeOverride(newParentConfig, resolvedConfig);
+ }
+ }
}
@Override
@@ -725,6 +774,9 @@ class WindowToken extends WindowContainer<WindowState> {
pw.print("fixedRotationConfig=");
pw.println(mFixedRotationTransformState.mRotatedOverrideConfiguration);
}
+ if (mIsExiting) {
+ pw.print(prefix); pw.println("isExiting=true");
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index fe267261b9e6..68834370c191 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -167,12 +167,7 @@ abstract class WindowTracing {
long token = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mGlobalLock) {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
- try {
- mService.dumpDebugLocked(os, logLevel);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
+ mService.dumpDebugLocked(os, logLevel);
}
os.end(token);
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
index 6e8094ac21d7..1b42e13039be 100644
--- a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -159,22 +159,28 @@ class WindowTracingPerfetto extends WindowTracing {
void onStart(WindowTracingDataSource.Config config) {
if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
+ Log.i(TAG, "Started session (frequency=FRAME, log level=" + config.mLogFrequency + ")");
mCountSessionsOnFrame.incrementAndGet();
} else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
+ Log.i(TAG, "Started session (frequency=TRANSACTION, log level="
+ + config.mLogFrequency + ")");
mCountSessionsOnTransaction.incrementAndGet();
}
- Log.i(TAG, "Started with logLevel: " + config.mLogLevel
- + " logFrequency: " + config.mLogFrequency);
+ Log.i(TAG, getStatus());
+
log(WHERE_START_TRACING);
}
void onStop(WindowTracingDataSource.Config config) {
if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
+ Log.i(TAG, "Stopped session (frequency=FRAME)");
mCountSessionsOnFrame.decrementAndGet();
+ Log.i(TAG, "Stopped session (frequency=TRANSACTION)");
} else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
mCountSessionsOnTransaction.decrementAndGet();
}
- Log.i(TAG, "Stopped");
+
+ Log.i(TAG, getStatus());
}
}
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index ce7776f270fd..6c5da175644b 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -81,4 +81,20 @@ public class RegionUtils {
Collections.reverse(rects);
rects.forEach(rectConsumer);
}
+
+ /** Returns the area size of the region. */
+ public static int getAreaSize(Region region) {
+ final RegionIterator regionIterator = new RegionIterator(region);
+ int area = 0;
+ final Rect rect = new Rect();
+ while (regionIterator.next(rect)) {
+ area += rect.width() * rect.height();
+ }
+ return area;
+ }
+
+ /** Returns whether the sizes between the two Rects are equal. */
+ public static boolean sizeEquals(Rect a, Rect b) {
+ return a.width() == b.width() && a.height() == b.height();
+ }
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 97ae6be0c2bc..85169b8a8752 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -188,7 +188,7 @@ cc_defaults {
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
- "android.hardware.thermal-V2-ndk",
+ "android.hardware.thermal-V3-ndk",
"android.hardware.tv.input@1.0",
"android.hardware.tv.input-V2-ndk",
"android.hardware.vibrator-V3-ndk",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5cd117b512d4..04642302ce45 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -56,6 +56,7 @@
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include <server_configurable_flags/get_flags.h>
+#include <ui/LogicalDisplayId.h>
#include <ui/Region.h>
#include <utils/Log.h>
#include <utils/Looper.h>
@@ -64,8 +65,10 @@
#include <atomic>
#include <cinttypes>
+#include <map>
#include <vector>
+#include "android_hardware_display_DisplayTopology.h"
#include "android_hardware_display_DisplayViewport.h"
#include "android_hardware_input_InputApplicationHandle.h"
#include "android_hardware_input_InputWindowHandle.h"
@@ -108,6 +111,7 @@ static struct {
jmethodID notifyInputDevicesChanged;
jmethodID notifyTouchpadHardwareState;
jmethodID notifyTouchpadGestureInfo;
+ jmethodID notifyTouchpadThreeFingerTap;
jmethodID notifySwitch;
jmethodID notifyInputChannelBroken;
jmethodID notifyNoFocusedWindowAnr;
@@ -207,6 +211,7 @@ static struct {
jfieldID lightTypePlayerId;
jfieldID lightTypeKeyboardBacklight;
jfieldID lightTypeKeyboardMicMute;
+ jfieldID lightTypeKeyboardVolumeMute;
jfieldID lightCapabilityBrightness;
jfieldID lightCapabilityColorRgb;
} gLightClassInfo;
@@ -317,6 +322,8 @@ public:
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
+ void setDisplayTopology(JNIEnv* env, jobject topologyGraph);
+
base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
const std::string& name,
@@ -335,15 +342,19 @@ public:
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
+ void setMouseReverseVerticalScrollingEnabled(bool enabled);
+ void setMouseSwapPrimaryButtonEnabled(bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
void setTouchpadTapDraggingEnabled(bool enabled);
void setShouldNotifyTouchpadHardwareState(bool enabled);
void setTouchpadRightClickZoneEnabled(bool enabled);
+ void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
+ void setTouchpadSystemGesturesEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
- void setInteractive(bool interactive);
+ void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
void reloadCalibration();
void reloadPointerIcons();
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
@@ -354,7 +365,7 @@ public:
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
- FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
+ vec2 getMouseCursorPosition(ui::LogicalDisplayId displayId);
void setStylusPointerIconEnabled(bool enabled);
void setInputMethodConnectionIsActive(bool isActive);
void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
@@ -366,6 +377,7 @@ public:
void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
int32_t deviceId) override;
void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override;
+ void notifyTouchpadThreeFingerTap() override;
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -432,7 +444,7 @@ public:
std::shared_ptr<PointerControllerInterface> createPointerController(
PointerControllerInterface::ControllerType type) override;
void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
- const FloatPoint& position) override;
+ const vec2& position) override;
void notifyMouseCursorFadedOnTyping() override;
/* --- InputFilterPolicyInterface implementation --- */
@@ -480,6 +492,12 @@ private:
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
+ // True if mouse vertical scrolling is reversed.
+ bool mouseReverseVerticalScrollingEnabled{false};
+
+ // True if the mouse primary button is swapped (left/right buttons).
+ bool mouseSwapPrimaryButtonEnabled{false};
+
// The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest).
int32_t touchpadPointerSpeed{0};
@@ -500,6 +518,13 @@ private:
// into context (a.k.a. "right") clicks.
bool touchpadRightClickZoneEnabled{false};
+ // True to use three-finger tap as a customizable shortcut; false to use it as a
+ // middle-click.
+ bool touchpadThreeFingerTapShortcutEnabled{false};
+
+ // True to enable system gestures (three- and four-finger swipes) on touchpads.
+ bool touchpadSystemGesturesEnabled{true};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -508,9 +533,11 @@ private:
// Keycodes to be remapped.
std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping{};
+
+ // Displays which are non-interactive.
+ std::set<ui::LogicalDisplayId> nonInteractiveDisplays;
} mLocked GUARDED_BY(mLock);
- std::atomic<bool> mInteractive;
void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
@@ -524,12 +551,13 @@ private:
void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
REQUIRES(mLock);
PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type);
+ bool isDisplayInteractive(ui::LogicalDisplayId displayId);
static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
};
NativeInputManager::NativeInputManager(jobject serviceObj, const sp<Looper>& looper)
- : mLooper(looper), mInteractive(true) {
+ : mLooper(looper) {
JNIEnv* env = jniEnv();
mServiceObj = env->NewGlobalRef(serviceObj);
@@ -547,9 +575,13 @@ NativeInputManager::~NativeInputManager() {
void NativeInputManager::dump(std::string& dump) {
dump += "Input Manager State:\n";
- dump += StringPrintf(INDENT "Interactive: %s\n", toString(mInteractive.load()));
{ // acquire lock
std::scoped_lock _l(mLock);
+ auto logicalDisplayIdToString = [](const ui::LogicalDisplayId& displayId) {
+ return std::to_string(displayId.val());
+ };
+ dump += StringPrintf(INDENT "Display not interactive: %s\n",
+ dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str());
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
@@ -611,6 +643,11 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
InputReaderConfiguration::Change::DISPLAY_INFO);
}
+void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) {
+ android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ // TODO(b/367661489): Use the topology
+}
+
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
const std::string& name) {
ATRACE_CALL();
@@ -753,12 +790,19 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
+ outConfig->mouseReverseVerticalScrollingEnabled =
+ mLocked.mouseReverseVerticalScrollingEnabled;
+ outConfig->mouseSwapPrimaryButtonEnabled = mLocked.mouseSwapPrimaryButtonEnabled;
+
outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed;
outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
+ outConfig->touchpadThreeFingerTapShortcutEnabled =
+ mLocked.touchpadThreeFingerTapShortcutEnabled;
+ outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -835,7 +879,7 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerCon
}
void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId,
- const FloatPoint& position) {
+ const vec2& position) {
// Notify the Reader so that devices can be reconfigured.
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1013,6 +1057,13 @@ void NativeInputManager::notifyTouchpadGestureInfo(enum GestureType type, int32_
checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo");
}
+void NativeInputManager::notifyTouchpadThreeFingerTap() {
+ ATRACE_CALL();
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadThreeFingerTap);
+ checkAndClearExceptionFromCallback(env, "notifyTouchpadThreeFingerTap");
+}
+
std::shared_ptr<KeyCharacterMap> NativeInputManager::getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) {
@@ -1308,6 +1359,36 @@ int32_t NativeInputManager::getMousePointerSpeed() {
return mLocked.pointerSpeed;
}
+void NativeInputManager::setMouseReverseVerticalScrollingEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseReverseVerticalScrollingEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseReverseVerticalScrollingEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
+void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseSwapPrimaryButtonEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseSwapPrimaryButtonEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1444,6 +1525,38 @@ void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadThreeFingerTapShortcutEnabled == enabled) {
+ return;
+ }
+
+ ALOGI("Setting touchpad three finger tap shortcut to %s.", toString(enabled));
+ mLocked.touchpadThreeFingerTapShortcutEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
+void NativeInputManager::setTouchpadSystemGesturesEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadSystemGesturesEnabled == enabled) {
+ return;
+ }
+
+ ALOGI("Setting touchpad system gestures enabled to %s.", toString(enabled));
+ mLocked.touchpadSystemGesturesEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -1476,8 +1589,10 @@ void NativeInputManager::requestPointerCapture(const sp<IBinder>& windowToken, b
mInputManager->getDispatcher().requestPointerCapture(windowToken, enabled);
}
-void NativeInputManager::setInteractive(bool interactive) {
- mInteractive = interactive;
+void NativeInputManager::setNonInteractiveDisplays(
+ const std::set<ui::LogicalDisplayId>& displayIds) {
+ std::scoped_lock _l(mLock);
+ mLocked.nonInteractiveDisplays = displayIds;
}
void NativeInputManager::reloadCalibration() {
@@ -1606,7 +1721,7 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent,
// - Ignore untrusted events and pass them along.
// - Ask the window manager what to do with normal events and trusted injected events.
// - For normal events wake and brighten the screen if currently off or dim.
- const bool interactive = mInteractive.load();
+ const bool interactive = isDisplayInteractive(keyEvent.getDisplayId());
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
@@ -1644,7 +1759,7 @@ void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId disp
// - No special filtering for injected events required at this time.
// - Filter normal events based on screen state.
// - For normal events brighten (but do not wake) the screen if currently dim.
- const bool interactive = mInteractive.load();
+ const bool interactive = isDisplayInteractive(displayId);
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
@@ -1683,6 +1798,24 @@ void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
}
}
+bool NativeInputManager::isDisplayInteractive(ui::LogicalDisplayId displayId) {
+ // If an input event doesn't have an associated id, use the default display id
+ if (displayId == ui::LogicalDisplayId::INVALID) {
+ displayId = ui::LogicalDisplayId::DEFAULT;
+ }
+
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ auto it = mLocked.nonInteractiveDisplays.find(displayId);
+ if (it != mLocked.nonInteractiveDisplays.end()) {
+ return false;
+ }
+ } // release lock
+
+ return true;
+}
+
nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token,
const KeyEvent& keyEvent,
uint32_t policyFlags) {
@@ -1898,7 +2031,7 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) {
InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
-FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
+vec2 NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
}
@@ -1967,6 +2100,12 @@ static void nativeSetDisplayViewports(JNIEnv* env, jobject nativeImplObj,
im->setDisplayViewports(env, viewportObjArray);
}
+static void nativeSetDisplayTopology(JNIEnv* env, jobject nativeImplObj,
+ jobject displayTopologyObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setDisplayTopology(env, displayTopologyObj);
+}
+
static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sourceMask, jint scanCode) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2226,6 +2365,12 @@ static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint device
im->getInputManager()->getReader().toggleCapsLockState(deviceId);
}
+static void resetLockedModifierState(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->getInputManager()->getReader().resetLockedModifierState();
+}
+
static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2366,16 +2511,35 @@ static void nativeSetTouchpadRightClickZoneEnabled(JNIEnv* env, jobject nativeIm
im->setTouchpadRightClickZoneEnabled(enabled);
}
+static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
+}
+
+static void nativeSetTouchpadSystemGesturesEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setTouchpadSystemGesturesEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setShowTouches(enabled);
}
-static void nativeSetInteractive(JNIEnv* env, jobject nativeImplObj, jboolean interactive) {
+static void nativeSetNonInteractiveDisplays(JNIEnv* env, jobject nativeImplObj,
+ jintArray displayIds) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setInteractive(interactive);
+ const std::vector displayIdsVec = getIntArray(env, displayIds);
+ std::set<ui::LogicalDisplayId> logicalDisplayIds;
+ for (int displayId : displayIdsVec) {
+ logicalDisplayIds.emplace(ui::LogicalDisplayId{displayId});
+ }
+
+ im->setNonInteractiveDisplays(logicalDisplayIds);
}
static void nativeReloadCalibration(JNIEnv* env, jobject nativeImplObj) {
@@ -2515,6 +2679,9 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId
} else if (lightInfo.type == InputDeviceLightType::KEYBOARD_MIC_MUTE) {
jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightTypeKeyboardMicMute);
+ } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_VOLUME_MUTE) {
+ jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
+ gLightClassInfo.lightTypeKeyboardVolumeMute);
} else {
ALOGW("Unknown light type %s", ftl::enum_string(lightInfo.type).c_str());
continue;
@@ -2966,6 +3133,24 @@ static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) {
return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId());
}
+static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseReverseVerticalScrollingEnabled(enabled);
+}
+
+static void nativeSetMouseSwapPrimaryButtonEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseSwapPrimaryButtonEnabled(enabled);
+}
+
+static jboolean nativeSetKernelWakeEnabled(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ return im->getInputManager()->getReader().setKernelWakeEnabled(deviceId, enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2977,6 +3162,8 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"start", "()V", (void*)nativeStart},
{"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
(void*)nativeSetDisplayViewports},
+ {"setDisplayTopology", "(Landroid/hardware/display/DisplayTopologyGraph;)V",
+ (void*)nativeSetDisplayTopology},
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
@@ -2997,6 +3184,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
(void*)nativeVerifyInputEvent},
{"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
+ {"resetLockedModifierState", "()V", (void*)resetLockedModifierState},
{"displayRemoved", "(I)V", (void*)nativeDisplayRemoved},
{"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
(void*)nativeSetFocusedApplication},
@@ -3012,6 +3200,9 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
+ {"setMouseReverseVerticalScrollingEnabled", "(Z)V",
+ (void*)nativeSetMouseReverseVerticalScrollingEnabled},
+ {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
(void*)nativeSetTouchpadNaturalScrollingEnabled},
@@ -3020,8 +3211,11 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setShouldNotifyTouchpadHardwareState", "(Z)V",
(void*)nativeSetShouldNotifyTouchpadHardwareState},
{"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
+ {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
+ (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
+ {"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
- {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
+ {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
{"vibrate", "(I[J[III)V", (void*)nativeVibrate},
{"vibrateCombined", "(I[JLandroid/util/SparseArray;II)V", (void*)nativeVibrateCombined},
@@ -3079,6 +3273,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
(void*)nativeSetAccessibilityStickyKeysEnabled},
{"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
{"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId},
+ {"setKernelWakeEnabled", "(IZ)Z", (void*)nativeSetKernelWakeEnabled},
};
#define FIND_CLASS(var, className) \
@@ -3129,6 +3324,8 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo",
"(II)V")
+ GET_METHOD_ID(gServiceClassInfo.notifyTouchpadThreeFingerTap, clazz,
+ "notifyTouchpadThreeFingerTap", "()V")
GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz,
"notifySwitch", "(JII)V");
@@ -3279,6 +3476,8 @@ int register_android_server_InputManager(JNIEnv* env) {
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_BACKLIGHT", "I");
gLightClassInfo.lightTypeKeyboardMicMute =
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_MIC_MUTE", "I");
+ gLightClassInfo.lightTypeKeyboardVolumeMute =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_VOLUME_MUTE", "I");
gLightClassInfo.lightCapabilityBrightness =
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_BRIGHTNESS", "I");
gLightClassInfo.lightCapabilityColorRgb =
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2836d46b0f1a..2add5b09f15b 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -349,7 +349,7 @@ class AnrTimerTracer {
return nullptr;
}
- // Return the currently watched pids. The lock must be held.
+ // Return the currently watched pids as a comma-separated list. The lock must be held.
std::string watchedPidsLocked() const {
if (watched_.size() == 0) return "none";
bool first = true;
@@ -357,6 +357,7 @@ class AnrTimerTracer {
for (auto i = watched_.cbegin(); i != watched_.cend(); i++) {
if (first) {
result += StringPrintf("%d", *i);
+ first = false;
} else {
result += StringPrintf(",%d", *i);
}
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 39c0c3ef1847..0ecc0a8a9524 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -41,6 +41,8 @@ namespace android {
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnComplete;
+static jclass sFrequencyProfileLegacyClass;
+static jmethodID sFrequencyProfileLegacyCtor;
static jclass sFrequencyProfileClass;
static jmethodID sFrequencyProfileCtor;
static struct {
@@ -53,6 +55,7 @@ static struct {
jmethodID setPrimitiveDelayMax;
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
+ jmethodID setFrequencyProfileLegacy;
jmethodID setFrequencyProfile;
jmethodID setMaxEnvelopeEffectSize;
jmethodID setMinEnvelopeEffectControlPointDurationMillis;
@@ -70,6 +73,11 @@ static struct {
jfieldID endFrequencyHz;
jfieldID duration;
} sRampClassInfo;
+static struct {
+ jfieldID amplitude;
+ jfieldID frequencyHz;
+ jfieldID timeMillis;
+} sPwlePointClassInfo;
static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
static_cast<uint8_t>(Aidl::EffectStrength::LIGHT));
@@ -179,6 +187,16 @@ static Aidl::ActivePwle activePwleFromJavaPrimitive(JNIEnv* env, jobject ramp) {
return pwle;
}
+static Aidl::PwleV2Primitive pwleV2PrimitiveFromJavaPrimitive(JNIEnv* env, jobject pwleObj) {
+ Aidl::PwleV2Primitive pwle;
+ pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.amplitude));
+ pwle.frequencyHz =
+ static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.frequencyHz));
+ pwle.timeMillis =
+ static_cast<int32_t>(env->GetIntField(pwleObj, sPwlePointClassInfo.timeMillis));
+ return pwle;
+}
+
/* Return true if braking is not NONE and the active PWLE starts and ends with zero amplitude. */
static bool shouldBeReplacedWithBraking(Aidl::ActivePwle activePwle, Aidl::Braking braking) {
return (braking != Aidl::Braking::NONE) && (activePwle.startAmplitude == 0) &&
@@ -396,6 +414,31 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt
return result.isOk() ? totalDuration.count() : (result.isUnsupported() ? 0 : -1);
}
+static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jobjectArray waveform, jlong vibrationId) {
+ VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
+ if (wrapper == nullptr) {
+ ALOGE("vibratorPerformPwleV2Effect failed because native wrapper was not initialized");
+ return -1;
+ }
+ size_t size = env->GetArrayLength(waveform);
+ Aidl::CompositePwleV2 composite;
+ std::vector<Aidl::PwleV2Primitive> primitives;
+ for (size_t i = 0; i < size; i++) {
+ jobject element = env->GetObjectArrayElement(waveform, i);
+ Aidl::PwleV2Primitive pwle = pwleV2PrimitiveFromJavaPrimitive(env, element);
+ primitives.push_back(pwle);
+ }
+ composite.pwlePrimitives = primitives;
+
+ auto callback = wrapper->createCallback(vibrationId);
+ auto composePwleV2Fn = [&composite, &callback](vibrator::HalWrapper* hal) {
+ return hal->composePwleV2(composite, callback);
+ };
+ auto result = wrapper->halCall<void>(composePwleV2Fn, "composePwleV2");
+ return result.isOk();
+}
+
static void vibratorAlwaysOnEnable(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong id,
jlong effect, jlong strength) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
@@ -517,11 +560,46 @@ static jboolean vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(),
reinterpret_cast<jfloat*>(amplitudes.data()));
}
- jobject frequencyProfile =
- env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
- minFrequency, frequencyResolution, maxAmplitudes);
- env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyProfile,
- frequencyProfile);
+ jobject frequencyProfileLegacy =
+ env->NewObject(sFrequencyProfileLegacyClass, sFrequencyProfileLegacyCtor,
+ resonantFrequency, minFrequency, frequencyResolution, maxAmplitudes);
+ env->CallObjectMethod(vibratorInfoBuilder,
+ sVibratorInfoBuilderClassInfo.setFrequencyProfileLegacy,
+ frequencyProfileLegacy);
+
+ if (info.frequencyToOutputAccelerationMap.isOk()) {
+ size_t mapSize = info.frequencyToOutputAccelerationMap.value().size();
+
+ jfloatArray frequenciesHz = env->NewFloatArray(mapSize);
+ jfloatArray outputAccelerationsGs = env->NewFloatArray(mapSize);
+
+ jfloat* frequenciesHzPtr = env->GetFloatArrayElements(frequenciesHz, nullptr);
+ jfloat* outputAccelerationsGsPtr =
+ env->GetFloatArrayElements(outputAccelerationsGs, nullptr);
+
+ size_t i = 0;
+ for (auto const& dataEntry : info.frequencyToOutputAccelerationMap.value()) {
+ frequenciesHzPtr[i] = static_cast<jfloat>(dataEntry.frequencyHz);
+ outputAccelerationsGsPtr[i] = static_cast<jfloat>(dataEntry.maxOutputAccelerationGs);
+ i++;
+ }
+
+ // Release the float pointers
+ env->ReleaseFloatArrayElements(frequenciesHz, frequenciesHzPtr, 0);
+ env->ReleaseFloatArrayElements(outputAccelerationsGs, outputAccelerationsGsPtr, 0);
+
+ jobject frequencyProfile =
+ env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
+ frequenciesHz, outputAccelerationsGs);
+
+ env->CallObjectMethod(vibratorInfoBuilder,
+ sVibratorInfoBuilderClassInfo.setFrequencyProfile, frequencyProfile);
+
+ // Delete local references to avoid memory leaks
+ env->DeleteLocalRef(frequenciesHz);
+ env->DeleteLocalRef(outputAccelerationsGs);
+ env->DeleteLocalRef(frequencyProfile);
+ }
return info.shouldRetry() ? JNI_FALSE : JNI_TRUE;
}
@@ -541,6 +619,8 @@ static const JNINativeMethod method_table[] = {
(void*)vibratorPerformComposedEffect},
{"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
(void*)vibratorPerformPwleEffect},
+ {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
+ (void*)vibratorPerformPwleV2Effect},
{"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
{"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
{"alwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable},
@@ -566,9 +646,21 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
+ jclass pwlePointClass = FindClassOrDie(env, "android/os/vibrator/PwlePoint");
+ sPwlePointClassInfo.amplitude = GetFieldIDOrDie(env, pwlePointClass, "mAmplitude", "F");
+ sPwlePointClassInfo.frequencyHz = GetFieldIDOrDie(env, pwlePointClass, "mFrequencyHz", "F");
+ sPwlePointClassInfo.timeMillis = GetFieldIDOrDie(env, pwlePointClass, "mTimeMillis", "I");
+
+ jclass frequencyProfileLegacyClass =
+ FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfileLegacy");
+ sFrequencyProfileLegacyClass =
+ static_cast<jclass>(env->NewGlobalRef(frequencyProfileLegacyClass));
+ sFrequencyProfileLegacyCtor =
+ GetMethodIDOrDie(env, sFrequencyProfileLegacyClass, "<init>", "(FFF[F)V");
+
jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile");
sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass));
- sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(FFF[F)V");
+ sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(F[F[F)V");
jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoBuilderClassInfo.setCapabilities =
@@ -598,6 +690,10 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sVibratorInfoBuilderClassInfo.setQFactor =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setQFactor",
"(F)Landroid/os/VibratorInfo$Builder;");
+ sVibratorInfoBuilderClassInfo.setFrequencyProfileLegacy =
+ GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfileLegacy",
+ "(Landroid/os/VibratorInfo$FrequencyProfileLegacy;)"
+ "Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setFrequencyProfile =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile",
"(Landroid/os/VibratorInfo$FrequencyProfile;)"
diff --git a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index a47ab9d27c17..46be79e7c097 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -16,27 +16,32 @@
#define LOG_TAG "VibratorManagerService"
-#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "core_jni_helpers.h"
-#include "jni.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
+#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <utils/misc.h>
-
#include <vibratorservice/VibratorManagerHalController.h>
-#include "com_android_server_vibrator_VibratorManagerService.h"
+#include <unordered_map>
+
+#include "android_runtime/AndroidRuntime.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
namespace android {
static JavaVM* sJvm = nullptr;
-static jmethodID sMethodIdOnComplete;
+static jmethodID sMethodIdOnSyncedVibrationComplete;
+static jmethodID sMethodIdOnVibrationSessionComplete;
static std::mutex gManagerMutex;
static vibrator::ManagerHalController* gManager GUARDED_BY(gManagerMutex) = nullptr;
class NativeVibratorManagerService {
public:
+ using IVibrationSession = aidl::android::hardware::vibrator::IVibrationSession;
+ using VibrationSessionConfig = aidl::android::hardware::vibrator::VibrationSessionConfig;
+
NativeVibratorManagerService(JNIEnv* env, jobject callbackListener)
: mHal(std::make_unique<vibrator::ManagerHalController>()),
mCallbackListener(env->NewGlobalRef(callbackListener)) {
@@ -52,15 +57,69 @@ public:
vibrator::ManagerHalController* hal() const { return mHal.get(); }
- std::function<void()> createCallback(jlong vibrationId) {
+ std::function<void()> createSyncedVibrationCallback(jlong vibrationId) {
return [vibrationId, this]() {
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
- jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, vibrationId);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnSyncedVibrationComplete,
+ vibrationId);
};
}
+ std::function<void()> createVibrationSessionCallback(jlong sessionId) {
+ return [sessionId, this]() {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnVibrationSessionComplete,
+ sessionId);
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ auto it = mSessions.find(sessionId);
+ if (it != mSessions.end()) {
+ mSessions.erase(it);
+ }
+ };
+ }
+
+ bool startSession(jlong sessionId, const std::vector<int32_t>& vibratorIds) {
+ VibrationSessionConfig config;
+ auto callback = createVibrationSessionCallback(sessionId);
+ auto result = hal()->startSession(vibratorIds, config, callback);
+ if (!result.isOk()) {
+ return false;
+ }
+
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ mSessions[sessionId] = std::move(result.value());
+ return true;
+ }
+
+ void closeSession(jlong sessionId) {
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ auto it = mSessions.find(sessionId);
+ if (it != mSessions.end()) {
+ it->second->close();
+ // Keep session, it can still be aborted.
+ }
+ }
+
+ void abortSession(jlong sessionId) {
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ auto it = mSessions.find(sessionId);
+ if (it != mSessions.end()) {
+ it->second->abort();
+ mSessions.erase(it);
+ }
+ }
+
+ void clearSessions() {
+ hal()->clearSessions();
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ mSessions.clear();
+ }
+
private:
+ std::mutex mSessionMutex;
const std::unique_ptr<vibrator::ManagerHalController> mHal;
+ std::unordered_map<jlong, std::shared_ptr<IVibrationSession>> mSessions
+ GUARDED_BY(mSessionMutex);
const jobject mCallbackListener;
};
@@ -142,7 +201,7 @@ static jboolean nativeTriggerSynced(JNIEnv* env, jclass /* clazz */, jlong servi
ALOGE("nativeTriggerSynced failed because native service was not initialized");
return JNI_FALSE;
}
- auto callback = service->createCallback(vibrationId);
+ auto callback = service->createSyncedVibrationCallback(vibrationId);
return service->hal()->triggerSynced(callback).isOk() ? JNI_TRUE : JNI_FALSE;
}
@@ -156,8 +215,47 @@ static void nativeCancelSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr
service->hal()->cancelSynced();
}
+static jboolean nativeStartSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
+ jlong sessionId, jintArray vibratorIds) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeStartSession failed because native service was not initialized");
+ return JNI_FALSE;
+ }
+ jsize size = env->GetArrayLength(vibratorIds);
+ std::vector<int32_t> ids(size);
+ env->GetIntArrayRegion(vibratorIds, 0, size, reinterpret_cast<jint*>(ids.data()));
+ return service->startSession(sessionId, ids) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void nativeEndSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong sessionId,
+ jboolean shouldAbort) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeEndSession failed because native service was not initialized");
+ return;
+ }
+ if (shouldAbort) {
+ service->abortSession(sessionId);
+ } else {
+ service->closeSession(sessionId);
+ }
+}
+
+static void nativeClearSessions(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeClearSessions failed because native service was not initialized");
+ return;
+ }
+ service->clearSessions();
+}
+
inline static constexpr auto sNativeInitMethodSignature =
- "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+ "(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)J";
static const JNINativeMethod method_table[] = {
{"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
@@ -167,15 +265,20 @@ static const JNINativeMethod method_table[] = {
{"nativePrepareSynced", "(J[I)Z", (void*)nativePrepareSynced},
{"nativeTriggerSynced", "(JJ)Z", (void*)nativeTriggerSynced},
{"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
+ {"nativeStartSession", "(JJ[I)Z", (void*)nativeStartSession},
+ {"nativeEndSession", "(JJZ)V", (void*)nativeEndSession},
+ {"nativeClearSessions", "(J)V", (void*)nativeClearSessions},
};
int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
sJvm = jvm;
auto listenerClassName =
- "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
+ "com/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks";
jclass listenerClass = FindClassOrDie(env, listenerClassName);
- sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
-
+ sMethodIdOnSyncedVibrationComplete =
+ GetMethodIDOrDie(env, listenerClass, "onSyncedVibrationComplete", "(J)V");
+ sMethodIdOnVibrationSessionComplete =
+ GetMethodIDOrDie(env, listenerClass, "onVibrationSessionComplete", "(J)V");
return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
method_table, NELEM(method_table));
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a12eb319b9fa..ddcff7bfafaf 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -49,6 +49,7 @@ int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_hardware_display_DisplayTopology(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_Freezer(JNIEnv* env);
@@ -115,6 +116,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
+ register_android_hardware_display_DisplayTopology(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_Freezer(env);
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 3b81f0a6191e..4c1ac39a5da0 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -178,4 +178,675 @@
column="51"/>
</issue>
-</issues> \ No newline at end of file
+ <issue
+ id="MissingPermissionAnnotation"
+ message="onShellCommand should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java"
+ line="128"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="monitorState should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="95"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="makeVisible should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="100"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="makeInvisible should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="105"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="enable should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="110"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="disable should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="115"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="getTimeoutTime should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="430"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="extendTimeRemaining should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="443"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="setVerificationPolicy should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="456"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="reportVerificationIncomplete should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="470"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="reportVerificationComplete should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="486"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="reportVerificationCompleteWithExtensionResponse should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="492"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mService.mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+ line="592"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+ line="1636"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+ line="1654"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="1820"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="1875"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="1980"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(REMOVE_TASKS, &quot;removeTask()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2116"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(REMOVE_TASKS, &quot;removeAllVisibleRecentTasks()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2144"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.FORCE_BACK,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2206"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2228"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2371"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, &quot;moveRootTaskToDisplay()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3103"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3157"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, &quot;unlock keyguard&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3640"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3683"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3701"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, &quot;updateConfiguration()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3904"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, &quot;getTaskSnapshot()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3978"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, &quot;takeTaskSnapshot()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4000"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4029"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4057"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4074"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, &quot;stopAppSwitches&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4136"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, &quot;resumeAppSwitches&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4147"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4198"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4215"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4280"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="5836"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="5849"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IColorDisplayManager permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" getContext().enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/display/color/ColorDisplayService.java"
+ line="2152"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+ line="779"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+ line="820"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+ line="906"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="INotificationManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (PERMISSION_GRANTED"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java"
+ line="6691"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="INotificationManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (PERMISSION_GRANTED"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java"
+ line="6712"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IOnDeviceIntelligenceManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java"
+ line="237"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageInstaller permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java"
+ line="1881"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageInstaller permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java"
+ line="1892"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingPermission(Manifest.permission.SEND_DEVICE_CUSTOMIZATION_READY,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
+ line="5798"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
+ line="6266"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="1406"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="1427"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="1734"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="2509"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="2564"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.ACCESS_TUNED_INFO)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="2932"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IUriGrantsManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java"
+ line="366"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IUriGrantsManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java"
+ line="444"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IVcnManagementService permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(DUMP, TAG);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/VcnManagementService.java"
+ line="1329"
+ column="9"/>
+ </issue>
+
+</issues>
diff --git a/services/core/services-jarjar-rules.txt b/services/core/services-jarjar-rules.txt
new file mode 100644
index 000000000000..0d296b238c7a
--- /dev/null
+++ b/services/core/services-jarjar-rules.txt
@@ -0,0 +1,2 @@
+# For profiling flags
+rule android.os.profiling.** android.internal.os.profiling.@1
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 0eafb59bdeac..6eac826f282c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -464,7 +464,7 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
- <xs:element name="customAnimationRateSec" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
+ <xs:element name="customAnimationRate" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
@@ -512,8 +512,6 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
- <!-- valid value of interpolation if specified: linear -->
- <xs:attribute name="interpolation" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="brightnessPoint">
@@ -714,6 +712,12 @@
minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
+ <!-- The time after which the stylus is to be assumed to be not under use. This will
+ enable the logic of changing the brightness with ambient light changes -->
+ <xs:element name="idleStylusTimeoutMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
@@ -951,6 +955,7 @@
<xs:enumeration value="default"/>
<xs:enumeration value="idle"/>
<xs:enumeration value="doze"/>
+ <xs:enumeration value="bedtime_wear"/>
</xs:restriction>
</xs:simpleType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 355b0ab15a62..a29e42cb480d 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,17 +8,20 @@ package com.android.server.display.config {
method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public boolean getEnabled();
+ method public final java.math.BigInteger getIdleStylusTimeoutMillis();
method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public void setEnabled(boolean);
+ method public final void setIdleStylusTimeoutMillis(java.math.BigInteger);
}
public enum AutoBrightnessModeName {
method public String getRawName();
enum_constant public static final com.android.server.display.config.AutoBrightnessModeName _default;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName bedtime_wear;
enum_constant public static final com.android.server.display.config.AutoBrightnessModeName doze;
enum_constant public static final com.android.server.display.config.AutoBrightnessModeName idle;
}
@@ -91,8 +94,6 @@ package com.android.server.display.config {
public class ComprehensiveBrightnessMap {
ctor public ComprehensiveBrightnessMap();
method @NonNull public final java.util.List<com.android.server.display.config.BrightnessPoint> getBrightnessPoint();
- method public String getInterpolation();
- method public void setInterpolation(String);
}
public class Density {
@@ -345,12 +346,12 @@ package com.android.server.display.config {
public class PowerThrottlingConfig {
ctor public PowerThrottlingConfig();
method @NonNull public final java.math.BigDecimal getBrightnessLowestCapAllowed();
- method @NonNull public final java.math.BigDecimal getCustomAnimationRateSec();
+ method @NonNull public final java.math.BigDecimal getCustomAnimationRate();
method @NonNull public final java.math.BigInteger getPollingWindowMaxMillis();
method @NonNull public final java.math.BigInteger getPollingWindowMinMillis();
method public final java.util.List<com.android.server.display.config.PowerThrottlingMap> getPowerThrottlingMap();
method public final void setBrightnessLowestCapAllowed(@NonNull java.math.BigDecimal);
- method public final void setCustomAnimationRateSec(@NonNull java.math.BigDecimal);
+ method public final void setCustomAnimationRate(@NonNull java.math.BigDecimal);
method public final void setPollingWindowMaxMillis(@NonNull java.math.BigInteger);
method public final void setPollingWindowMinMillis(@NonNull java.math.BigInteger);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index e73dacbed9a3..0c9a89bb0a30 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
+import android.credentials.flags.Flags;
import android.credentials.selection.DisabledProviderData;
import android.credentials.selection.IntentCreationResult;
import android.credentials.selection.IntentFactory;
@@ -46,6 +47,12 @@ import java.util.UUID;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
+
+ private static final String SESSION_ID_TRACK_ONE =
+ "com.android.server.credentials.CredentialManagerUi.SESSION_ID_TRACK_ONE";
+ private static final String SESSION_ID_TRACK_TWO =
+ "com.android.server.credentials.CredentialManagerUi.SESSION_ID_TRACK_TWO";
+
@NonNull
private final CredentialManagerUiCallback mCallbacks;
@NonNull
@@ -148,8 +155,8 @@ public class CredentialManagerUi {
* by the calling app process. The bottom-sheet navigates to the default page when the intent
* is invoked.
*
- * @param requestInfo the information about the request
- * @param providerDataList the list of provider data from remote providers
+ * @param requestInfo the information about the request
+ * @param providerDataList the list of provider data from remote providers
*/
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList,
@@ -175,6 +182,11 @@ public class CredentialManagerUi {
mContext, intentCreationResult, mUserId);
Intent intent = intentCreationResult.getIntent();
intent.setAction(UUID.randomUUID().toString());
+ if (Flags.frameworkSessionIdMetricBundle()) {
+ intent.putExtra(SESSION_ID_TRACK_ONE,
+ requestSessionMetric.getInitialPhaseMetric().getSessionIdCaller());
+ intent.putExtra(SESSION_ID_TRACK_TWO, requestSessionMetric.getSessionIdTrackTwo());
+ }
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
return PendingIntent.getActivityAsUser(
@@ -192,8 +204,8 @@ public class CredentialManagerUi {
* each autofill id and passed in as extras in the pending intent set as authentication
* of the pinned entry.
*
- * @param requestInfo the information about the request
- * @param requestSessionMetric the metric object for logging
+ * @param requestInfo the information about the request
+ * @param requestSessionMetric the metric object for logging
*/
public Intent createIntentForAutofill(RequestInfo requestInfo,
RequestSessionMetric requestSessionMetric) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index b982098fefa4..76d16e19e774 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -371,6 +371,9 @@ class ActiveAdmin {
}
ActiveAdmin(int userId, boolean permissionBased) {
+ if (Flags.activeAdminCleanup()) {
+ throw new UnsupportedOperationException("permission based admin no longer supported");
+ }
if (permissionBased == false) {
throw new IllegalArgumentException("Can only pass true for permissionBased admin");
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 395ea9176877..c937e10a28ce 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
@@ -124,17 +125,18 @@ class DevicePolicyData {
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
- // Some DevicePolicyManager APIs can be called by (1) a DPC or (2) an app with permissions that
- // isn't a DPC. For the latter, the caller won't have to provide a ComponentName and won't be
- // mapped to an ActiveAdmin. This permission-based admin should be used to persist policies
- // set by the permission-based caller. This admin should not be added to mAdminMap or mAdminList
- // since a lot of methods in DPMS assume the ActiveAdmins here have a valid ComponentName.
- // Instead, use variants of DPMS active admin getters to include the permission-based admin.
+ /**
+ * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
+ */
+ @Deprecated
ActiveAdmin mPermissionBasedAdmin;
// Create or get the permission-based admin. The permission-based admin will not have a
// DeviceAdminInfo or ComponentName.
ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
+ if (Flags.activeAdminCleanup()) {
+ throw new UnsupportedOperationException("permission based admin no longer supported");
+ }
if (mPermissionBasedAdmin == null) {
mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
}
@@ -147,7 +149,7 @@ class DevicePolicyData {
// This is the list of component allowed to start lock task mode.
List<String> mLockTaskPackages = new ArrayList<>();
- /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */
+ /** @deprecated moved to DevicePolicyEngine. */
@Deprecated
@Nullable
List<String> mUserControlDisabledPackages;
@@ -280,7 +282,7 @@ class DevicePolicyData {
}
}
- if (policyData.mPermissionBasedAdmin != null) {
+ if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
out.startTag(null, "permission-based-admin");
policyData.mPermissionBasedAdmin.writeToXml(out);
out.endTag(null, "permission-based-admin");
@@ -521,7 +523,8 @@ class DevicePolicyData {
} catch (RuntimeException e) {
Slogf.w(TAG, e, "Failed loading admin %s", name);
}
- } else if ("permission-based-admin".equals(tag)) {
+ } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
+
ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
ap.readFromXml(parser, /* overwritePolicies= */ false);
policy.mPermissionBasedAdmin = ap;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 4beb6a8a3480..1c8d06e52329 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -42,7 +42,6 @@ import android.app.admin.PolicyUpdateReceiver;
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
import android.app.admin.UserRestrictionPolicyKey;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -91,6 +90,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
/**
* Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -99,10 +99,11 @@ import java.util.Set;
final class DevicePolicyEngine {
static final String TAG = "DevicePolicyEngine";
- // TODO(b/281701062): reference role name from role manager once its exposed.
static final String DEVICE_LOCK_CONTROLLER_ROLE =
"android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+ static final String SYSTEM_SUPERVISION_ROLE = "android.app.role.SYSTEM_SUPERVISION";
+
private static final String CELLULAR_2G_USER_RESTRICTION_ID =
DevicePolicyIdentifiers.getIdentifierForUserRestriction(
UserManager.DISALLOW_CELLULAR_2G);
@@ -151,19 +152,20 @@ final class DevicePolicyEngine {
mAdminPolicySize = new SparseArray<>();
}
- private void maybeForceEnforcementRefreshLocked(@NonNull PolicyDefinition<?> policyDefinition) {
+ private void forceEnforcementRefreshIfUserRestrictionLocked(
+ @NonNull PolicyDefinition<?> policyDefinition) {
try {
- if (shouldForceEnforcementRefresh(policyDefinition)) {
+ if (isUserRestrictionPolicy(policyDefinition)) {
// This is okay because it's only true for user restrictions which are all <Boolean>
forceEnforcementRefreshLocked((PolicyDefinition<Boolean>) policyDefinition);
}
} catch (Throwable e) {
// Catch any possible exceptions just to be on the safe side
- Log.e(TAG, "Exception throw during maybeForceEnforcementRefreshLocked", e);
+ Log.e(TAG, "Exception thrown during forceEnforcementRefreshIfUserRestrictionLocked", e);
}
}
- private boolean shouldForceEnforcementRefresh(@NonNull PolicyDefinition<?> policyDefinition) {
+ private boolean isUserRestrictionPolicy(@NonNull PolicyDefinition<?> policyDefinition) {
// These are all "not nullable" but for the purposes of maximum safety for a lightly tested
// change we check here
if (policyDefinition == null) {
@@ -256,7 +258,7 @@ final class DevicePolicyEngine {
// No need to notify admins as no new policy is actually enforced, we're just filling in
// the data structures.
if (!skipEnforcePolicy) {
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (policyChanged) {
onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
@@ -346,7 +348,7 @@ final class DevicePolicyEngine {
Objects.requireNonNull(enforcingAdmin);
synchronized (mLock) {
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
return;
}
@@ -516,7 +518,7 @@ final class DevicePolicyEngine {
// No need to notify admins as no new policy is actually enforced, we're just filling in
// the data structures.
if (!skipEnforcePolicy) {
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (policyChanged) {
onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
@@ -569,7 +571,7 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (policyChanged) {
onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
@@ -1031,11 +1033,11 @@ final class DevicePolicyEngine {
}
}
- private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition,
+ private <V> CompletableFuture<Boolean> enforcePolicy(PolicyDefinition<V> policyDefinition,
@Nullable PolicyValue<V> policyValue, int userId) {
// null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
- policyDefinition.enforcePolicy(
+ return policyDefinition.enforcePolicy(
policyValue == null ? null : policyValue.getValue(), mContext, userId);
}
@@ -1235,6 +1237,8 @@ final class DevicePolicyEngine {
}
}
for (EnforcingAdmin admin : admins) {
+ // No need to make changes to system enforcing admins.
+ if (admin.isSystemAuthority()) break;
if (updatedPackage == null || updatedPackage.equals(admin.getPackageName())) {
if (!isPackageInstalled(admin.getPackageName(), userId)) {
Slogf.i(TAG, String.format(
@@ -2100,17 +2104,13 @@ final class DevicePolicyEngine {
String tag = parser.getName();
switch (tag) {
case TAG_POLICY_KEY_ENTRY:
- if (Flags.dontReadPolicyDefinition()) {
- policyDefinition = PolicyDefinition.readFromXml(parser);
- if (policyDefinition != null) {
- policyKey = policyDefinition.getPolicyKey();
- }
- } else {
- policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ policyDefinition = PolicyDefinition.readFromXml(parser);
+ if (policyDefinition != null) {
+ policyKey = policyDefinition.getPolicyKey();
}
break;
case TAG_POLICY_STATE_ENTRY:
- if (Flags.dontReadPolicyDefinition() && policyDefinition == null) {
+ if (policyDefinition == null) {
Slogf.w(TAG, "Skipping policy state - unknown policy definition");
} else {
policyState = PolicyState.readFromXml(policyDefinition, parser);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6314b8564c54..2627895b8c63 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -26,6 +26,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
@@ -117,6 +118,7 @@ import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK;
import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
@@ -125,6 +127,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEV
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
+import static android.app.admin.DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
@@ -255,7 +258,6 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -332,6 +334,7 @@ import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyDrawableResource;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.AppFunctionsPolicy;
import android.app.admin.DevicePolicyManager.DeviceOwnerType;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.DevicePolicyManager.OperationSafetyReason;
@@ -377,6 +380,7 @@ import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.annotation.ChangeId;
@@ -462,7 +466,6 @@ import android.permission.PermissionControllerManager;
import android.provider.CalendarContract;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Telephony;
@@ -506,6 +509,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.notification.SystemNotificationChannels;
@@ -578,6 +582,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -591,6 +596,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Implementation of the device policy APIs.
@@ -717,24 +723,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.add(Settings.Secure.LOCATION_MODE);
GLOBAL_SETTINGS_ALLOWLIST = new ArraySet<>();
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_WIFI_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME_ZONE);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.DATA_ROAMING);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_SLEEP_POLICY);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_MODE);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_WIFI_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME_ZONE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.DATA_ROAMING);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.USB_MASS_STORAGE_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_SLEEP_POLICY);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.STAY_ON_WHILE_PLUGGED_IN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.PRIVATE_DNS_MODE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(PRIVATE_DNS_SPECIFIER);
GLOBAL_SETTINGS_DEPRECATED = new ArraySet<>();
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.BLUETOOTH_ON);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.MODE_RINGER);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.NETWORK_PREFERENCE);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.WIFI_ON);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.BLUETOOTH_ON);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.DEVELOPMENT_SETTINGS_ENABLED);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.MODE_RINGER);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.NETWORK_PREFERENCE);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.WIFI_ON);
SYSTEM_SETTINGS_ALLOWLIST = new ArraySet<>();
SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS);
@@ -777,7 +783,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Strings logged with {@link
- * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+ * MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
* {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
* {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
* {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
@@ -788,11 +794,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* For admin apps targeting R+, throw when the app sets password requirement
* that is not taken into account at given quality. For example when quality is set
- * to {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
+ * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
* make sense to require certain password length. If the intent is to require a password of
* certain length having at least NUMERIC quality, the admin should first call
- * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} and only then call
- * {@link android.app.admin.DevicePolicyManager#setPasswordMinimumLength}.
+ * {@link DevicePolicyManager#setPasswordQuality} and only then call
+ * {@link DevicePolicyManager#setPasswordMinimumLength}.
*
* <p>Conversely when an admin app targeting R+ lowers password quality, those
* requirements that stop making sense are reset to default values.
@@ -803,9 +809,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Admin apps targeting Android R+ may not use
- * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated
- * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use
- * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}.
+ * {@link DevicePolicyManager#setSecureSetting} to change the deprecated
+ * {@link Settings.Secure#LOCATION_MODE} setting. Instead they should use
+ * {@link DevicePolicyManager#setLocationEnabled}.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
@@ -851,7 +857,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private @interface CopyAccountStatus {}
/**
- * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+ * Mapping of {@link DevicePolicyManager.ApplicationExemptionConstants} to
* corresponding app-ops.
*/
private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
@@ -883,11 +889,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Admin apps targeting Android S+ may not use
- * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
+ * {@link DevicePolicyManager#setPasswordQuality} to set password quality
* on the {@code DevicePolicyManager} instance obtained by calling
- * {@link android.app.admin.DevicePolicyManager#getParentProfileInstance}.
+ * {@link DevicePolicyManager#getParentProfileInstance}.
* Instead, they should use
- * {@link android.app.admin.DevicePolicyManager#setRequiredPasswordComplexity} to set
+ * {@link DevicePolicyManager#setRequiredPasswordComplexity} to set
* coarse-grained password requirements device-wide.
*/
@ChangeId
@@ -896,7 +902,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* For Admin Apps targeting U+
- * If {@link android.security.IKeyChainService#setGrant} is called with an alias with no
+ * If {@link IKeyChainService#setGrant} is called with an alias with no
* existing key, throw IllegalArgumentException.
*/
@ChangeId
@@ -908,10 +914,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
- private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
- "enable_permission_based_access";
- private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
-
private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
/**
@@ -932,6 +934,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final UsageStatsManagerInternal mUsageStatsManagerInternal;
final TelephonyManager mTelephonyManager;
final RoleManager mRoleManager;
+ final SupervisionManagerInternal mSupervisionManagerInternal;
+
private final LockPatternUtils mLockPatternUtils;
private final LockSettingsInternal mLockSettingsInternal;
private final DeviceAdminServiceController mDeviceAdminServiceController;
@@ -1480,8 +1484,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (packageName == null || packageName.equals(adminPackage)) {
if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null
|| mIPackageManager.getReceiverInfo(aa.info.getComponent(),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE,
userHandle) == null) {
Slogf.e(LOG_TAG, String.format(
"Admin package %s not found for user %d, removing active admin",
@@ -1699,7 +1703,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
}
- Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+ Context createContextAsUser(UserHandle user) throws NameNotFoundException {
final String packageName = mContext.getPackageName();
return mContext.createPackageContextAsUser(packageName, 0, user);
}
@@ -2011,25 +2015,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
- Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Global.putStringForUser(mContext.getContentResolver(),
name, value, userHandle);
}
int settingsGlobalGetInt(String name, int def) {
- return Settings.Global.getInt(mContext.getContentResolver(), name, def);
+ return Global.getInt(mContext.getContentResolver(), name, def);
}
@Nullable
String settingsGlobalGetString(String name) {
- return Settings.Global.getString(mContext.getContentResolver(), name);
+ return Global.getString(mContext.getContentResolver(), name);
}
void settingsGlobalPutInt(String name, int value) {
- Settings.Global.putInt(mContext.getContentResolver(), name, value);
+ Global.putInt(mContext.getContentResolver(), name, value);
}
void settingsGlobalPutString(String name, String value) {
- Settings.Global.putString(mContext.getContentResolver(), name, value);
+ Global.putString(mContext.getContentResolver(), name, value);
}
void settingsSystemPutStringForUser(String name, String value, int userId) {
@@ -2088,6 +2092,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
boolean isAdminInstalledCaCertAutoApproved() {
return false;
}
+
+ @Nullable
+ SupervisionManagerInternal getSupervisionManager() {
+ return LocalServices.getService(SupervisionManagerInternal.class);
+ }
}
/**
@@ -2119,6 +2128,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mIPermissionManager = Objects.requireNonNull(injector.getIPermissionManager());
mTelephonyManager = Objects.requireNonNull(injector.getTelephonyManager());
mRoleManager = Objects.requireNonNull(injector.getRoleManager());
+ if (Flags.secondaryLockscreenApiEnabled()) {
+ mSupervisionManagerInternal = injector.getSupervisionManager();
+ } else {
+ mSupervisionManagerInternal = null;
+ }
mLocalService = new LocalService();
mLockPatternUtils = injector.newLockPatternUtils();
@@ -2240,7 +2254,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return Collections.unmodifiableSet(packageNames);
}
-
private @Nullable String getDefaultRoleHolderPackageName(int resId) {
String packageNameAndSignature = mContext.getString(resId);
@@ -3200,8 +3213,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mIPackageManager.getReceiverInfo(adminName,
GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+ | MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
} catch (RemoteException e) {
// shouldn't happen.
Slogf.wtf(LOG_TAG, "Error getting receiver info", e);
@@ -3212,9 +3225,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
- if (!permission.BIND_DEVICE_ADMIN.equals(ai.permission)) {
+ if (!BIND_DEVICE_ADMIN.equals(ai.permission)) {
final String message = "DeviceAdminReceiver " + adminName + " must be protected with "
- + permission.BIND_DEVICE_ADMIN;
+ + BIND_DEVICE_ADMIN;
Slogf.w(LOG_TAG, message);
if (throwForMissingPermission &&
ai.applicationInfo.targetSdkVersion > Build.VERSION_CODES.M) {
@@ -3486,7 +3499,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@GuardedBy("getLockObject()")
private boolean maybeMigrateSuspendedPackagesLocked(String backupId) {
Slog.i(LOG_TAG, "Migrating suspended packages to policy engine");
- if (!Flags.unmanagedModeMigration()) {
+ if (!Flags.suspendPackagesCoexistence()) {
return false;
}
if (mOwners.isSuspendedPackagesMigrated()) {
@@ -3515,6 +3528,88 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateResetPasswordTokenLocked(String backupId) {
+ if (!Flags.resetPasswordWithTokenCoexistence()) {
+ Slog.i(LOG_TAG, "ResetPasswordWithToken not migrated because coexistence "
+ + "support is not enabled.");
+ return false;
+ }
+ if (mOwners.isResetPasswordWithTokenMigrated()) {
+ // TODO(b/359187209): Remove log after Flags.resetPasswordWithTokenCoexistence full
+ // rollout.
+ Slog.v(LOG_TAG, "ResetPasswordWithToken was previously migrated to "
+ + "policy engine.");
+ return false;
+ }
+
+ Slog.i(LOG_TAG, "Migrating ResetPasswordWithToken to policy engine");
+
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ int userId = enforcingAdmin.getUserId();
+ DevicePolicyData policy = getUserData(userId);
+ if (policy.mPasswordTokenHandle != 0) {
+ Slog.i(LOG_TAG, "Setting RESET_PASSWORD_TOKEN policy");
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ new LongPolicyValue(policy.mPasswordTokenHandle),
+ userId);
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG,
+ "Failed to migrate ResetPasswordWithToken to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking ResetPasswordWithToken migration complete");
+ mOwners.markResetPasswordWithTokenMigrated();
+ return true;
+ }
+
+
+
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
+ if (!Flags.setMtePolicyCoexistence()) {
+ Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
+ + "support is disabled.");
+ return false;
+ }
+ if (mOwners.isMemoryTaggingMigrated()) {
+ // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
+ Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
+ return false;
+ }
+
+ Slog.i(LOG_TAG, "Migrating Memory Tagging to policy engine");
+
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ if (admin.mtePolicy != 0) {
+ Slog.i(LOG_TAG, "Setting Memory Tagging policy");
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ enforcingAdmin,
+ new IntegerPolicyValue(admin.mtePolicy),
+ true /* No need to re-set system properties */);
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG,
+ "Failed to migrate Memory Tagging to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking Memory Tagging migration complete");
+ mOwners.markMemoryTaggingMigrated();
+ return true;
+ }
+
/** Register callbacks for statsd pulled atoms. */
private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -3890,7 +3985,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if ((admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+ if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
+ || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
&& admin.passwordExpirationTimeout > 0L
&& now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
&& admin.passwordExpirationDate > 0L) {
@@ -4110,8 +4206,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private void checkAllUsersAreAffiliatedWithDevice() {
- Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(),
- "operation not allowed when device has unaffiliated users");
+ synchronized (getLockObject()) {
+ Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(),
+ "operation not allowed when device has unaffiliated users");
+ }
}
@Override
@@ -4321,8 +4419,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final ApplicationInfo ai;
try {
ai = mInjector.getIPackageManager().getApplicationInfo(packageName,
- (PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
+ (MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE), userHandle);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -4602,22 +4700,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@GuardedBy("getLockObject()")
private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) {
if (isSeparateProfileChallengeEnabled(userHandle)) {
-
- if (isPermissionCheckFlagEnabled()) {
- return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle);
- }
// If this user has a separate challenge, only return its restrictions.
return getUserDataUnchecked(userHandle).mAdminList;
}
// If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile
// we need to query the parent user who owns the credential.
- if (isPermissionCheckFlagEnabled()) {
- return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle),
- (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
- } else {
- return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
- (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
- }
+ return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
+ (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
}
@@ -4640,33 +4729,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
(user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id));
}
- /**
- * Get the list of active admins for an affected user:
- * <ul>
- * <li>The active admins associated with the userHandle itself</li>
- * <li>The parent active admins for each managed profile associated with the userHandle</li>
- * <li>The permission based admin associated with the userHandle itself</li>
- * </ul>
- *
- * @param userHandle the affected user for whom to get the active admins
- * @return the list of active admins for the affected user
- */
- @GuardedBy("getLockObject()")
- private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
- int userHandle) {
- List<ActiveAdmin> list;
-
- if (isManagedProfile(userHandle)) {
- list = getUserDataUnchecked(userHandle).mAdminList;
- }
- list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle,
- /* shouldIncludeProfileAdmins */ (user) -> false);
-
- if (getUserData(userHandle).mPermissionBasedAdmin != null) {
- list.add(getUserData(userHandle).mPermissionBasedAdmin);
- }
- return list;
- }
/**
* Returns the list of admins on the given user, as well as parent admins for each managed
@@ -4719,44 +4781,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users);
}
- /**
- * Returns the list of admins on the given user, as well as parent admins for each managed
- * profile associated with the given user. Optionally also include the admin of each managed
- * profile.
- * <p> Should not be called on a profile user.
- */
- @GuardedBy("getLockObject()")
- private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle,
- Predicate<UserInfo> shouldIncludeProfileAdmins) {
- ArrayList<ActiveAdmin> admins = new ArrayList<>();
- mInjector.binderWithCleanCallingIdentity(() -> {
- for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
- DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
- if (userInfo.id == userHandle) {
- admins.addAll(policy.mAdminList);
- if (policy.mPermissionBasedAdmin != null) {
- admins.add(policy.mPermissionBasedAdmin);
- }
- } else if (userInfo.isManagedProfile()) {
- for (int i = 0; i < policy.mAdminList.size(); i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
- if (admin.hasParentActiveAdmin()) {
- admins.add(admin.getParentActiveAdmin());
- }
- if (shouldIncludeProfileAdmins.test(userInfo)) {
- admins.add(admin);
- }
- }
- if (policy.mPermissionBasedAdmin != null
- && shouldIncludeProfileAdmins.test(userInfo)) {
- admins.add(policy.mPermissionBasedAdmin);
- }
- }
- }
- });
- return admins;
- }
-
private boolean isSeparateProfileChallengeEnabled(int userHandle) {
return mInjector.binderWithCleanCallingIdentity(() ->
mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle));
@@ -4849,25 +4873,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(who, "ComponentName is null");
- }
+ Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms");
int userHandle = mInjector.userHandleGetCallingUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- ap = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- caller.getPackageName(), affectedUserId)
- .getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
- }
+ ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
// Calling this API automatically bumps the expiration date
final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L;
ap.passwordExpirationDate = expiration;
@@ -4928,28 +4942,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
String packageName) {
- CallerIdentity caller;
+ CallerIdentity caller = getCallerIdentity(admin);
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
- ActiveAdmin activeAdmin;
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller));
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- caller.getUserId());
- activeAdmin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- synchronized (getLockObject()) {
- activeAdmin = getProfileOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin activeAdmin;
+ synchronized (getLockObject()) {
+ activeAdmin = getProfileOwnerLocked(caller.getUserId());
}
List<String> changedProviders = null;
@@ -4982,28 +4982,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
String packageName) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
+ CallerIdentity caller = getCallerIdentity(admin);
- ActiveAdmin activeAdmin;
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller));
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- caller.getUserId());
- activeAdmin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- synchronized (getLockObject()) {
- activeAdmin = getProfileOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin activeAdmin;
+ synchronized (getLockObject()) {
+ activeAdmin = getProfileOwnerLocked(caller.getUserId());
}
List<String> changedProviders = null;
@@ -5036,27 +5022,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public List<String> getCrossProfileWidgetProviders(ComponentName admin,
String callerPackageName) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
- ActiveAdmin activeAdmin;
+ CallerIdentity caller = getCallerIdentity(admin);
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- caller.getUserId());
- activeAdmin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- synchronized (getLockObject()) {
- activeAdmin = getProfileOwnerLocked(caller.getUserId());
- }
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller));
+
+ ActiveAdmin activeAdmin;
+ synchronized (getLockObject()) {
+ activeAdmin = getProfileOwnerLocked(caller.getUserId());
}
synchronized (getLockObject()) {
@@ -5405,24 +5378,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforceUserUnlocked(userHandle, parent);
synchronized (getLockObject()) {
- if (isPermissionCheckFlagEnabled()) {
- int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
- enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- callerPackageName, affectedUser);
- } else {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(
- null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- }
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(
+ null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
int credentialOwner = getCredentialOwner(userHandle, parent);
DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
final int userToCheck = getProfileParentUserIfRequested(userHandle, parent);
- boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
+ return isActivePasswordSufficientForUserLocked(
policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck);
- return activePasswordSufficientForUserLocked;
}
}
@@ -5578,21 +5544,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method on parent.");
} else {
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS)
- || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
- "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " +
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS
- + " permissions, or be a profile owner or device owner.");
- } else {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
- "Must have " + REQUEST_PASSWORD_COMPLEXITY
- + " permission, or be a profile owner or device owner.");
- }
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
+ "Must have " + REQUEST_PASSWORD_COMPLEXITY
+ + " permission, or be a profile owner or device owner.");
}
synchronized (getLockObject()) {
@@ -5627,13 +5583,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getUserId());
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
- ActiveAdmin activeAdmin = admin.getActiveAdmin();
+ final ActiveAdmin activeAdmin;
+ if (Flags.activeAdminCleanup()) {
+ if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+ synchronized (getLockObject()) {
+ activeAdmin = getActiveAdminUncheckedLocked(
+ admin.getComponentName(), admin.getUserId());
+ }
+ } else {
+ activeAdmin = null;
+ }
+ } else {
+ activeAdmin = admin.getActiveAdmin();
+ }
// We require the caller to explicitly clear any password quality requirements set
// on the parent DPM instance, to avoid the case where password requirements are
// specified in the form of quality on the parent but complexity on the profile
// itself.
- if (!calledOnParent) {
+ if (activeAdmin != null && !calledOnParent) {
final boolean hasQualityRequirementsOnParent = activeAdmin.hasParentActiveAdmin()
&& activeAdmin.getParentActiveAdmin().mPasswordPolicy.quality
!= PASSWORD_QUALITY_UNSPECIFIED;
@@ -5657,20 +5625,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mInjector.binderWithCleanCallingIdentity(() -> {
- // Reset the password policy.
- if (calledOnParent) {
- activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
- } else {
- activeAdmin.mPasswordPolicy = new PasswordPolicy();
+ if (activeAdmin != null) {
+ // Reset the password policy.
+ if (calledOnParent) {
+ activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
+ } else {
+ activeAdmin.mPasswordPolicy = new PasswordPolicy();
+ }
+ updatePasswordQualityCacheForUserGroup(caller.getUserId());
}
+
synchronized (getLockObject()) {
updatePasswordValidityCheckpointLocked(caller.getUserId(), calledOnParent);
}
- updatePasswordQualityCacheForUserGroup(caller.getUserId());
saveSettingsLocked(caller.getUserId());
});
-
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_COMPLEXITY)
.setAdmin(caller.getPackageName())
@@ -5684,26 +5654,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void setRequiredPasswordComplexityPreCoexistence(
String callerPackageName, int passwordComplexity, boolean calledOnParent) {
CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (!isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwner(caller));
- Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
- }
+
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- // TODO: Make sure this returns the parent of the fake admin
- // TODO: Deal with null componentname
- int affectedUser = calledOnParent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- admin = enforcePermissionAndGetEnforcingAdmin(
- null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- caller.getPackageName(), affectedUser).getActiveAdmin();
- } else {
- admin = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
- }
+ admin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
if (admin.mPasswordComplexity != passwordComplexity) {
// We require the caller to explicitly clear any password quality requirements set
@@ -5863,14 +5822,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!isSystemUid(caller)) {
// This API can be called by an active device admin or by keyguard code.
if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) {
- if (isPermissionCheckFlagEnabled()) {
- int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
- enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- callerPackageName, affectedUser);
- } else {
- getActiveAdminForCallerLocked(
- null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
- }
+ getActiveAdminForCallerLocked(
+ null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
}
}
@@ -5887,31 +5840,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(who, "ComponentName is null");
- }
-
+ Objects.requireNonNull(who, "ComponentName is null");
int userId = mInjector.userHandleGetCallingUserId();
int affectedUserId = parent ? getProfileParentId(userId) : userId;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- ap = enforcePermissionAndGetEnforcingAdmin(
- who,
- /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA,
- /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA,
- caller.getPackageName(), affectedUserId).getActiveAdmin();
- } else {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
- }
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
+ ActiveAdmin ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
if (ap.maximumFailedPasswordsForWipe != num) {
ap.maximumFailedPasswordsForWipe = num;
@@ -6059,7 +5999,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(admin != null,
"Unauthorized caller cannot call resetPassword.");
if (getTargetSdk(admin.info.getPackageName(),
- userHandle) <= android.os.Build.VERSION_CODES.M) {
+ userHandle) <= Build.VERSION_CODES.M) {
Slogf.e(LOG_TAG, "Device admin can no longer call resetPassword()");
return false;
}
@@ -6166,25 +6106,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(who, "ComponentName is null");
- }
+
+ Objects.requireNonNull(who, "ComponentName is null");
+
int userHandle = mInjector.userHandleGetCallingUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- ap = enforcePermissionAndGetEnforcingAdmin(
- who,
- /*permission=*/ MANAGE_DEVICE_POLICY_LOCK,
- /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
- caller.getPackageName(),
- affectedUserId).getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
- }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
if (ap.maximumTimeToUnlock != timeMs) {
ap.maximumTimeToUnlock = timeMs;
@@ -6220,7 +6149,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (policy.mLastMaximumTimeToLock != Long.MAX_VALUE) {
// Make sure KEEP_SCREEN_ON is disabled, since that
// would allow bypassing of the maximum time to lock.
- mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+ mInjector.settingsGlobalPutInt(Global.STAY_ON_WHILE_PLUGGED_IN, 0);
}
getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(parentId, timeMs);
});
@@ -6290,16 +6219,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
+
Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwner(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+
// timeoutMs with value 0 means that the admin doesn't participate
// timeoutMs is clamped to the interval in case the internal constants change in the future
final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -6313,17 +6239,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int userHandle = caller.getUserId();
boolean changed = false;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- int affectedUser = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- ap = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- caller.getPackageName(), affectedUser).getActiveAdmin();
- } else {
- ap = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
- }
+ ActiveAdmin ap = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
if (ap.strongAuthUnlockTimeout != timeoutMs) {
ap.strongAuthUnlockTimeout = timeoutMs;
saveSettingsLocked(userHandle);
@@ -6395,7 +6312,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void lockNow(int flags, String callerPackageName, boolean parent) {
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (Flags.lockNowCoexistence()) {
caller = getCallerIdentity(callerPackageName);
} else {
caller = getCallerIdentity();
@@ -6404,28 +6321,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int callingUserId = caller.getUserId();
ComponentName adminComponent = null;
synchronized (getLockObject()) {
- ActiveAdmin admin;
// Make sure the caller has any active admin with the right policy or
// the required permission.
- if (isUnicornFlagEnabled()) {
- admin = enforcePermissionsAndGetEnforcingAdmin(
+ if (Flags.lockNowCoexistence()) {
+ EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
/* admin= */ null,
/* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE},
/* deviceAdminPolicy= */ USES_POLICY_FORCE_LOCK,
caller.getPackageName(),
getAffectedUser(parent)
- ).getActiveAdmin();
+ );
+ if (Flags.activeAdminCleanup()) {
+ adminComponent = enforcingAdmin.getComponentName();
+ } else {
+ ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
+ adminComponent = admin == null ? null : admin.info.getComponent();
+ }
} else {
- admin = getActiveAdminOrCheckPermissionForCallerLocked(
+ ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
- DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
+ USES_POLICY_FORCE_LOCK,
parent,
LOCK_DEVICE);
+ adminComponent = admin == null ? null : admin.info.getComponent();
}
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOCK_NOW);
final long ident = mInjector.binderClearCallingIdentity();
try {
- adminComponent = admin == null ? null : admin.info.getComponent();
if (adminComponent != null) {
// For Profile Owners only, callers with only permission not allowed.
if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
@@ -6620,16 +6542,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
+ "management app is not allowed to install a user selectable key pair");
@@ -6689,16 +6604,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -6758,13 +6666,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean canInstallCertificates(CallerIdentity caller) {
- if (isPermissionCheckFlagEnabled()) {
- return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId());
- } else {
- return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
- || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
- }
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
}
private boolean canChooseCertificates(CallerIdentity caller) {
@@ -6957,16 +6860,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getPackageName(), caller.getUid()));
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
- caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
- isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
+ caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
+ isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -7099,16 +6995,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -7593,7 +7482,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* privileged APIs.
* <p>
* This is done by checking that the calling package is authorized to perform the app operation
- * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}.
+ * {@link AppOpsManager#OP_MANAGE_CREDENTIALS}.
*
* @param caller the calling identity
* @return {@code true} if the calling process is the credential management app.
@@ -7603,7 +7492,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
AppOpsManager appOpsManager = mInjector.getAppOpsManager();
if (appOpsManager == null) return false;
return appOpsManager.noteOpNoThrow(AppOpsManager.OP_MANAGE_CREDENTIALS, caller.getUid(),
- caller.getPackageName(), null, null) == AppOpsManager.MODE_ALLOWED;
+ caller.getPackageName(), null, null) == MODE_ALLOWED;
});
}
@@ -7914,7 +7803,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void wipeDataWithReason(String callerPackageName, int flags,
@NonNull String wipeReasonForUser, boolean calledOnParentInstance,
boolean factoryReset) {
- if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
+ if (!mHasFeature && !hasCallingOrSelfPermission(MASTER_CLEAR)) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -7927,7 +7816,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
USES_POLICY_WIPE_DATA,
caller.getPackageName(),
factoryReset ? UserHandle.USER_ALL : getAffectedUser(calledOnParentInstance));
- ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_WIPE_DATA);
@@ -7936,10 +7824,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
}
- int userId = admin != null ? admin.getUserHandle().getIdentifier()
- : caller.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
- userId);
+ int userId;
+ ActiveAdmin admin = null;
+ if (Flags.activeAdminCleanup()) {
+ userId = enforcingAdmin.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+ enforcingAdmin, userId);
+ } else {
+ admin = enforcingAdmin.getActiveAdmin();
+ userId = admin != null ? admin.getUserHandle().getIdentifier()
+ : caller.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
+ userId);
+ }
+
if (calledByProfileOwnerOnOrgOwnedDevice) {
// When wipeData is called on the parent instance, it implies wiping the entire device.
if (calledOnParentInstance) {
@@ -7960,25 +7858,36 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final String adminName;
final ComponentName adminComp;
- if (admin != null) {
- if (admin.isPermissionBased) {
+ if (Flags.activeAdminCleanup()) {
+ adminComp = enforcingAdmin.getComponentName();
+ adminName = adminComp != null
+ ? adminComp.flattenToShortString()
+ : enforcingAdmin.getPackageName();
+ event.setAdmin(enforcingAdmin.getPackageName());
+ // Not including any HSUM handling here because the "else" branch in the "flag off"
+ // case below is unreachable under normal circumstances and for permission-based
+ // callers admin won't be null.
+ } else {
+ if (admin != null) {
+ if (admin.isPermissionBased) {
+ adminComp = null;
+ adminName = caller.getPackageName();
+ event.setAdmin(adminName);
+ } else {
+ adminComp = admin.info.getComponent();
+ adminName = adminComp.flattenToShortString();
+ event.setAdmin(adminComp);
+ }
+ } else {
adminComp = null;
- adminName = caller.getPackageName();
+ adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
+ Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
event.setAdmin(adminName);
- } else {
- adminComp = admin.info.getComponent();
- adminName = adminComp.flattenToShortString();
- event.setAdmin(adminComp);
- }
- } else {
- adminComp = null;
- adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
- Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
- event.setAdmin(adminName);
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- // On headless system user mode, the call is meant to factory reset the whole
- // device, otherwise the caller could simply remove the current user.
- userId = UserHandle.USER_SYSTEM;
+ if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ // On headless system user mode, the call is meant to factory reset the whole
+ // device, otherwise the caller could simply remove the current user.
+ userId = UserHandle.USER_SYSTEM;
+ }
}
}
event.write();
@@ -7987,8 +7896,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
adminName, calledByProfileOwnerOnOrgOwnedDevice);
- wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
- calledOnParentInstance, factoryReset);
+ wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId, factoryReset);
}
private String getGenericWipeReason(
@@ -8144,17 +8052,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* factory reset
*/
private void wipeDataNoLock(@Nullable ComponentName admin, int flags, String internalReason,
- String wipeReasonForUser, int userId, boolean calledOnParentInstance,
- @Nullable Boolean factoryReset) {
+ String wipeReasonForUser, int userId, @Nullable Boolean factoryReset) {
wtfIfInLock();
final String adminPackage;
if (admin != null) {
adminPackage = admin.getPackageName();
} else {
- int callerId = mInjector.binderGetCallingUid();
- String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerId);
+ int callerUid = mInjector.binderGetCallingUid();
+ String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerUid);
Preconditions.checkState(adminPackages.length > 0,
- "Caller %s does not have any associated packages", callerId);
+ "Caller %s does not have any associated packages", callerUid);
adminPackage = adminPackages[0];
}
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -8176,32 +8083,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new SecurityException("Cannot wipe data. " + restriction
+ " restriction is set for user " + userId);
}
- });
- boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
- boolean isMainUser = userId == getMainUserId();
- boolean wipeDevice;
- if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
- adminPackage,
- userId)) {
- // Legacy mode
- wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
- } else {
- // Explicit behaviour
- if (factoryReset) {
- EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
- /*admin=*/ null,
- /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
- MASTER_CLEAR},
- USES_POLICY_WIPE_DATA,
- adminPackage,
- factoryReset ? UserHandle.USER_ALL :
- getAffectedUser(calledOnParentInstance));
- wipeDevice = true;
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean isMainUser = userId == getMainUserId();
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ adminPackage,
+ userId)) {
+ // Legacy mode
+ wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
} else {
- mInjector.binderWithCleanCallingIdentity(() -> {
- Preconditions.checkCallAuthorization(!isSystemUser,
+ // Explicit behaviour
+ if (factoryReset) {
+ wipeDevice = true;
+ } else {
+ Preconditions.checkState(!isSystemUser,
"User %s is a system user and cannot be removed", userId);
boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
&& mUserManager.getAliveUsers().stream()
@@ -8209,13 +8106,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.noneMatch(UserInfo::isFull);
Preconditions.checkState(!isLastNonHeadlessUser,
"Removing user %s would leave the device without any active users. "
- + "Consider factory resetting the device instead.",
- userId);
- });
- wipeDevice = false;
+ + "Consider factory resetting the device instead.", userId);
+ wipeDevice = false;
+ }
}
- }
- mInjector.binderWithCleanCallingIdentity(() -> {
+
if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
@@ -8255,29 +8150,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Preconditions.checkNotNull(who, "ComponentName is null");
- }
+
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- if (!isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+
checkCanExecuteOrThrowUnsafe(DevicePolicyManager
.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
synchronized (getLockObject()) {
ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(),
- UserHandle.USER_ALL)
- .getActiveAdmin();
- } else {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
admin.mFactoryResetProtectionPolicy = policy;
saveSettingsLocked(caller.getUserId());
}
@@ -8314,10 +8201,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (getLockObject()) {
if (who == null) {
Preconditions.checkCallAuthorization(frpManagementAgentUid == caller.getUid()
- || hasCallingPermission(permission.MASTER_CLEAR)
+ || hasCallingPermission(MASTER_CLEAR)
|| hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
"Must be called by the FRP management agent on device");
- admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
} else {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller)
@@ -8488,7 +8375,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
for (int i = 0; i < admins.size(); i++) {
ActiveAdmin admin = admins.get(i);
- if (admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+ if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
+ || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
affectedUserIds.add(admin.getUserHandle().getIdentifier());
long timeout = admin.passwordExpirationTimeout;
admin.passwordExpirationDate =
@@ -8556,7 +8444,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/* reason= */ "reportFailedPasswordAttempt()",
getFailedPasswordAttemptWipeMessage(),
userId,
- /* calledOnParentInstance= */ parent,
// factoryReset=null to enable U- behaviour
/* factoryReset= */ null);
} catch (SecurityException e) {
@@ -8583,7 +8470,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
final int userId = admin.getUserHandle().getIdentifier();
- if (admin.isPermissionBased) {
+ if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
return userId;
}
final ComponentName component = admin.info.getComponent();
@@ -8801,9 +8688,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
return;
}
- mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
- mInjector.settingsGlobalPutInt(Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
- mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
+ mInjector.settingsGlobalPutInt(Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
+ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
exclusionList);
}
@@ -8924,7 +8811,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final int rawStatus = getEncryptionStatus();
- if ((rawStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
+ if ((rawStatus == ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
}
return rawStatus;
@@ -8948,7 +8835,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private int getEncryptionStatus() {
if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
- return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+ return ENCRYPTION_STATUS_ACTIVE_PER_USER;
} else {
return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
}
@@ -9143,7 +9030,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Turn AUTO_TIME on in settings if it is required
if (required) {
mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME,
+ () -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME,
1 /* AUTO_TIME on */));
}
DevicePolicyEventLogger
@@ -9177,26 +9064,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
-
- CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
-
- if (isUnicornFlagEnabled()) {
- // The effect of this policy is device-wide.
- enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
-
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME)
.setAdmin(caller.getPackageName())
@@ -9212,65 +9086,98 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return false;
}
- CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
+ CallerIdentity caller = getCallerIdentity(who);
- if (isUnicornFlagEnabled()) {
- enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
+ if (Flags.setAutoTimeEnabledCoexistence()) {
+ Preconditions.checkCallAuthorization(hasPermission(SET_TIME, callerPackageName));
} else {
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
}
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
/**
- * Set whether auto time zone is enabled on the device.
+ * Set whether auto time is enabled on the device.
*/
@Override
- public void setAutoTimeZoneEnabled(@Nullable ComponentName who, String callerPackageName,
- boolean enabled) {
+ public void setAutoTimePolicy(String callerPackageName, int policy) {
if (!mHasFeature) {
return;
}
- CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
+ final Set<Integer> allowedValues =
+ Set.of(
+ DevicePolicyManager.AUTO_TIME_ENABLED,
+ DevicePolicyManager.AUTO_TIME_DISABLED,
+ DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY);
+ Preconditions.checkArgument(
+ allowedValues.contains(policy), "Provided mode is not one of the allowed values.");
- if (isUnicornFlagEnabled()) {
- // The effect of this policy is device-wide.
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- SET_TIME_ZONE,
- caller.getPackageName(),
- UserHandle.USER_ALL
- );
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME,
+ caller.getPackageName(),
+ UserHandle.USER_ALL
+ );
+ if (policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.removeGlobalPolicy(PolicyDefinition.AUTO_TIME, enforcingAdmin);
+ } else {
mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.AUTO_TIMEZONE,
- // TODO(b/260573124): add correct enforcing admin when permission changes are
- // merged.
+ PolicyDefinition.AUTO_TIME,
enforcingAdmin,
- new BooleanPolicyValue(enabled));
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+ new IntegerPolicyValue(policy));
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_AUTO_TIME)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ENABLED)
+ .write();
+ }
+ }
+
+ /**
+ * Returns whether auto time is used on the device or not.
+ */
+ @Override
+ public int getAutoTimePolicy(String callerPackageName) {
+ if (!mHasFeature) {
+ return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+ }
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME,
+ callerPackageName,
+ UserHandle.USER_ALL
+ );
+ Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.AUTO_TIME, enforcingAdmin);
+ return state != null ? state : DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ /**
+ * Set whether auto time zone is enabled on the device.
+ */
+ @Override
+ public void setAutoTimeZoneEnabled(@Nullable ComponentName who, String callerPackageName,
+ boolean enabled) {
+ if (!mHasFeature) {
+ return;
}
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
.setAdmin(caller.getPackageName())
@@ -9287,24 +9194,73 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
- CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
+ CallerIdentity caller = getCallerIdentity(who);
+ if (Flags.setAutoTimeZoneEnabledCoexistence()) {
+ Preconditions.checkCallAuthorization(
+ hasPermission(SET_TIME_ZONE, callerPackageName));
} else {
- caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
+ return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
+ }
+
+ /**
+ * Set auto time zone state.
+ */
+ public void setAutoTimeZonePolicy(String callerPackageName, int policy) {
+ if (!mHasFeature) {
+ return;
}
- if (isUnicornFlagEnabled()) {
- // The effect of this policy is device-wide.
- enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME_ZONE,
+ caller.getPackageName(),
+ UserHandle.USER_ALL
+ );
+
+ if (policy != DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.AUTO_TIME_ZONE,
+ enforcingAdmin,
+ new IntegerPolicyValue(policy));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ZONE_ENABLED)
+ .write();
} else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.AUTO_TIME_ZONE,
+ enforcingAdmin);
}
+ }
- return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
+ /**
+ * Returns whether auto time zone is used on the device or not.
+ */
+ @Override
+ public int getAutoTimeZonePolicy(String callerPackageName) {
+ if (!mHasFeature) {
+ return DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+ }
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME_ZONE,
+ callerPackageName,
+ UserHandle.USER_ALL
+ );
+ Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.AUTO_TIME_ZONE, enforcingAdmin);
+ return state != null ? state : DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
}
// TODO (b/137101239): remove this method in follow-up CL
@@ -9544,7 +9500,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
@@ -9554,7 +9510,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int userHandle = caller.getUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
- if (isUnicornFlagEnabled()) {
+ if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
// SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9633,7 +9589,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (getLockObject()) {
if (who != null) {
- if (isUnicornFlagEnabled()) {
+ if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
EnforcingAdmin admin = getEnforcingAdminForPackage(
who, who.getPackageName(), userHandle);
Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
@@ -9652,7 +9608,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// the different behaviour between a profile with separate challenge vs a profile with
// unified challenge, which was part of getActiveAdminsForLockscreenPoliciesLocked()
// before the migration.
- if (isUnicornFlagEnabled()) {
+ if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
Integer features = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
affectedUserId);
@@ -10218,15 +10174,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return admin;
}
- ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() {
- ensureLocked();
- ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (isPermissionCheckFlagEnabled() && doOrPo == null) {
- return getUserData(0).mPermissionBasedAdmin;
- }
- return doOrPo;
- }
-
@Override
public void clearDeviceOwner(String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
@@ -10523,7 +10470,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mDelegationMap.clear();
policy.mStatusBarDisabled = false;
policy.mSecondaryLockscreenEnabled = false;
- policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
+ policy.mUserProvisioningState = STATE_USER_UNMANAGED;
policy.mAffiliationIds.clear();
resetAffiliationCacheLocked();
policy.mLockTaskPackages.clear();
@@ -10558,7 +10505,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public int getUserProvisioningState(int userHandle) {
if (!mHasFeature) {
- return DevicePolicyManager.STATE_USER_UNMANAGED;
+ return STATE_USER_UNMANAGED;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(canManageUsers(caller)
@@ -10613,7 +10560,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// ADB shell can only move directly from un-managed to finalized as part of
// directly setting profile-owner or device-owner.
if (getUserProvisioningState(userId)
- != DevicePolicyManager.STATE_USER_UNMANAGED
+ != STATE_USER_UNMANAGED
|| newState != STATE_USER_SETUP_FINALIZED) {
throw new IllegalStateException("Not allowed to change provisioning state "
+ "unless current provisioning state is unmanaged, and new state"
@@ -10651,9 +10598,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
// Valid transitions for normal use-cases.
switch (currentState) {
- case DevicePolicyManager.STATE_USER_UNMANAGED:
+ case STATE_USER_UNMANAGED:
// Can move to any state from unmanaged (except itself as an edge case)..
- if (newState != DevicePolicyManager.STATE_USER_UNMANAGED) {
+ if (newState != STATE_USER_UNMANAGED) {
return;
}
break;
@@ -10677,7 +10624,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
break;
case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED:
// Should only move to an unmanaged state after removing the work profile.
- if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
+ if (newState == STATE_USER_UNMANAGED) {
return;
}
break;
@@ -10969,8 +10916,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* (2.1.1) The caller is the profile owner.
* (2.1.2) The caller is from another app in the same user as the profile owner, AND
* the caller is the delegated cert installer.
- * (3) The caller holds the
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
*
* For the device owner case, simply check that the caller is the device owner or the
* delegated certificate installer.
@@ -10984,24 +10929,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@VisibleForTesting
boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid);
- // TODO(b/280048070): Introduce a permission to handle device ID access
- if (isPermissionCheckFlagEnabled()
- && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
- return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
- } else {
- ComponentName deviceOwner = getDeviceOwnerComponent(true);
- if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
- || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
- return true;
- }
- ComponentName profileOwner = getProfileOwnerAsUser(userId);
- final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
- && (profileOwner.getPackageName().equals(packageName)
- || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
- if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
- || isUserAffiliatedWithDevice(userId))) {
- return true;
- }
+ ComponentName deviceOwner = getDeviceOwnerComponent(true);
+ if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
+ || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+ return true;
+ }
+ ComponentName profileOwner = getProfileOwnerAsUser(userId);
+ final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
+ && (profileOwner.getPackageName().equals(packageName)
+ || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
+ if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
+ || isUserAffiliatedWithDevice(userId))) {
+ return true;
}
return false;
}
@@ -11057,7 +10996,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
UserHandle userHandle = UserHandle.of(userId);
userContext = mContext.createPackageContextAsUser(packageName, /* flags= */ 0,
userHandle);
- } catch (PackageManager.NameNotFoundException nnfe) {
+ } catch (NameNotFoundException nnfe) {
Slogf.w(LOG_TAG, nnfe, "%s is not installed for user %d", packageName, userId);
return null;
}
@@ -11277,20 +11216,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean canQueryAdminPolicy(CallerIdentity caller) {
- return hasCallingOrSelfPermission(permission.QUERY_ADMIN_POLICY);
+ return hasCallingOrSelfPermission(QUERY_ADMIN_POLICY);
}
private boolean hasPermission(String permission, int pid, int uid) {
- return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
}
private boolean hasCallingPermission(String permission) {
- return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
}
private boolean hasCallingOrSelfPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
}
private boolean hasPermissionForPreflight(CallerIdentity caller, String permission) {
@@ -11320,7 +11259,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (mOwners.hasDeviceOwner()) {
return false;
}
-
+
final ComponentName profileOwner = getProfileOwnerAsUser(userId);
if (profileOwner == null) {
return false;
@@ -11329,7 +11268,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (isManagedProfile(userId)) {
return false;
}
-
+
return true;
}
private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
@@ -11596,7 +11535,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private String getEncryptionStatusName(int encryptionStatus) {
switch (encryptionStatus) {
- case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER:
+ case ENCRYPTION_STATUS_ACTIVE_PER_USER:
return "per-user";
case DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED:
return "unsupported";
@@ -11702,25 +11641,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setDefaultSmsApplication(ComponentName admin, String callerPackageName,
String packageName, boolean parent) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
+ CallerIdentity caller = getCallerIdentity(admin);
- final int userId;
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(
- MANAGE_DEVICE_POLICY_DEFAULT_SMS,
- caller.getPackageName(),
- getAffectedUser(parent));
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
if (!parent && isManagedProfile(caller.getUserId())
&& getManagedSubscriptionsPolicy().getPolicyType()
@@ -11730,6 +11656,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ "ManagedSubscriptions policy is set");
}
+ final int userId;
if (parent) {
userId = getProfileParentId(mInjector.userHandleGetCallingUserId());
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -11845,75 +11772,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new IllegalArgumentException("Invalid package name: " + validationResult);
}
- if (isUnicornFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- caller.getPackageName(),
- caller.getUserId()
- );
-
+ final boolean isRoleHolder;
+ if (who != null) {
+ // DO or PO
+ Preconditions.checkCallAuthorization(
+ (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ Preconditions.checkCallAuthorization(!parent,
+ "DO or PO cannot call this on parent");
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Delegates, or the DMRH. Only DMRH can call this on COPE parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ // DMRH caller uses policy engine, others still use legacy code path
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
if (restrictions == null || restrictions.isEmpty()) {
mDevicePolicyEngine.removeLocalPolicy(
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
enforcingAdmin,
- caller.getUserId());
+ affectedUserId);
} else {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
enforcingAdmin,
new BundlePolicyValue(restrictions),
- caller.getUserId());
+ affectedUserId);
}
- setBackwardsCompatibleAppRestrictions(
- caller, packageName, restrictions, caller.getUserHandle());
} else {
- final boolean isRoleHolder;
- if (who != null) {
- // DO or PO
- Preconditions.checkCallAuthorization(
- (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
- Preconditions.checkCallAuthorization(!parent,
- "DO or PO cannot call this on parent");
- // Caller has opted to be treated as DPC (by passing a non-null who), so don't
- // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
- isRoleHolder = false;
- } else {
- // Delegates, or the DMRH. Only DMRH can call this on COPE parent
- isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
- if (parent) {
- Preconditions.checkCallAuthorization(isRoleHolder);
- Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
- "Role Holder can only operate parent app restriction on COPE devices");
- } else {
- Preconditions.checkCallAuthorization(isRoleHolder
- || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
- }
- }
- // DMRH caller uses policy engine, others still use legacy code path
- if (isRoleHolder) {
- EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
- caller.getPackageName());
- int affectedUserId = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- if (restrictions == null || restrictions.isEmpty()) {
- mDevicePolicyEngine.removeLocalPolicy(
- PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- enforcingAdmin,
- affectedUserId);
- } else {
- mDevicePolicyEngine.setLocalPolicy(
- PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- enforcingAdmin,
- new BundlePolicyValue(restrictions),
- affectedUserId);
- }
- } else {
- mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setApplicationRestrictions(packageName, restrictions,
- caller.getUserHandle());
- });
- }
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ mUserManager.setApplicationRestrictions(packageName, restrictions,
+ caller.getUserHandle());
+ });
}
DevicePolicyEventLogger
@@ -11924,31 +11827,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.write();
}
- /**
- * Set app restrictions in user manager for DPC callers only to keep backwards compatibility
- * for the old getApplicationRestrictions API.
- */
- private void setBackwardsCompatibleAppRestrictions(
- CallerIdentity caller, String packageName, Bundle restrictions, UserHandle userHandle) {
- if ((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))) {
- Bundle restrictionsToApply = restrictions == null || restrictions.isEmpty()
- ? getAppRestrictionsSetByAnyAdmin(packageName, userHandle)
- : restrictions;
- mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setApplicationRestrictions(packageName, restrictionsToApply,
- userHandle);
- });
- } else {
- // Notify package of changes via an intent - only sent to explicitly registered
- // receivers. Sending here because For DPCs, this is being sent in UMS.
- final Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
- changeIntent.setPackage(packageName);
- changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(changeIntent, userHandle);
- }
- }
-
private Bundle getAppRestrictionsSetByAnyAdmin(String packageName, UserHandle userHandle) {
LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
@@ -11977,10 +11855,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(admin, "admin is null");
- }
-
+ Objects.requireNonNull(admin, "admin is null");
Objects.requireNonNull(agent, "agent is null");
PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName());
@@ -11992,19 +11867,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
- int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- ap = enforcePermissionAndGetEnforcingAdmin(
- admin,
- /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD,
- /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES,
- caller.getPackageName(), affectedUserId).getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(admin,
- DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
- }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION);
@@ -12100,27 +11964,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName,
IntentFilter filter, int flags) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
- int callingUserId = caller.getUserId();
+ CallerIdentity caller = getCallerIdentity(who);
+
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- callingUserId);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- }
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
+ int callingUserId = caller.getUserId();
UserInfo parent = mUserManager.getProfileParent(callingUserId);
if (parent == null) {
Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no "
@@ -12164,28 +12017,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
- int callingUserId = caller.getUserId();
+ CallerIdentity caller = getCallerIdentity(who);
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- callingUserId);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
+ int callingUserId = caller.getUserId();
UserInfo parent = mUserManager.getProfileParent(callingUserId);
if (parent == null) {
Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no "
@@ -12776,7 +12617,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if ((flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
+ USER_SETUP_COMPLETE, 1, userHandle);
}
sendProvisioningCompletedBroadcast(
@@ -13228,65 +13069,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (isUnicornFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- caller.getPackageName(),
- caller.getUserId()
- );
-
+ final boolean isRoleHolder;
+ if (who != null) {
+ // Caller is DO or PO. They cannot call this on parent
+ Preconditions.checkCallAuthorization(!parent
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Caller is delegates or the DMRH. Only DMRH can call this on parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- caller.getUserId());
- if (policies.isEmpty() || !policies.containsKey(enforcingAdmin)) {
+ affectedUserId);
+ if (!policies.containsKey(enforcingAdmin)) {
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
} else {
- final boolean isRoleHolder;
- if (who != null) {
- // Caller is DO or PO. They cannot call this on parent
- Preconditions.checkCallAuthorization(!parent
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
- // Caller has opted to be treated as DPC (by passing a non-null who), so don't
- // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
- isRoleHolder = false;
- } else {
- // Caller is delegates or the DMRH. Only DMRH can call this on parent
- isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
- if (parent) {
- Preconditions.checkCallAuthorization(isRoleHolder);
- Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
- "Role Holder can only operate parent app restriction on COPE devices");
- } else {
- Preconditions.checkCallAuthorization(isRoleHolder
- || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
- }
- }
- if (isRoleHolder) {
- EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
- caller.getPackageName());
- int affectedUserId = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
- mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
- PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- affectedUserId);
- if (!policies.containsKey(enforcingAdmin)) {
- return Bundle.EMPTY;
- }
- return policies.get(enforcingAdmin).getValue();
- } else {
- return mInjector.binderWithCleanCallingIdentity(() -> {
- Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
- caller.getUserHandle());
- // if no restrictions were saved, mUserManager.getApplicationRestrictions
- // returns null, but DPM method should return an empty Bundle as per JavaDoc
- return bundle != null ? bundle : Bundle.EMPTY;
- });
- }
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
+ caller.getUserHandle());
+ // if no restrictions were saved, mUserManager.getApplicationRestrictions
+ // returns null, but DPM method should return an empty Bundle as per JavaDoc
+ return bundle != null ? bundle : Bundle.EMPTY;
+ });
}
}
@@ -13328,21 +13151,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
ActiveAdmin admin;
- if (isUnicornFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller,
- DELEGATION_PACKAGE_ACCESS)));
- synchronized (getLockObject()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller,
+ DELEGATION_PACKAGE_ACCESS)));
+ synchronized (getLockObject()) {
+ admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
}
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
@@ -13407,7 +13221,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public String[] setPackagesSuspended(ComponentName who, String callerPackage,
String[] packageNames, boolean suspended) {
- if (!Flags.unmanagedModeMigration()) {
+ if (!Flags.suspendPackagesCoexistence()) {
return setPackagesSuspendedPreCoexistence(who, callerPackage, packageNames, suspended);
}
@@ -13497,7 +13311,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (Flags.unmanagedModeMigration()) {
+ if (Flags.suspendPackagesCoexistence()) {
enforcePermission(
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
caller.getPackageName(),
@@ -13616,10 +13430,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Objects.requireNonNull(systemEntity);
CallerIdentity caller = getCallerIdentity();
- if (caller.getUid() != Process.SYSTEM_UID) {
+ if (!isSystemUid(caller)) {
throw new SecurityException("Only system services can call setUserRestrictionForUser"
+ " on a target user: " + targetUser);
}
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ throw new IllegalArgumentException("Invalid restriction key: " + key);
+ }
if (VERBOSE_LOG) {
Slogf.v(LOG_TAG, "Creating SystemEnforcingAdmin %s for calling package %s",
systemEntity, caller.getPackageName());
@@ -13752,6 +13569,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller,
UserHandle.USER_ALL);
}
+
+ @Override
+ public void setUserRestrictionGloballyFromSystem(@NonNull String systemEntity, String key,
+ boolean enabled) {
+ Objects.requireNonNull(systemEntity);
+
+ CallerIdentity caller = getCallerIdentity();
+ if (!isSystemUid(caller)) {
+ throw new SecurityException("Only system services can call"
+ + " setUserRestrictionGloballyFromSystem");
+ }
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ throw new IllegalArgumentException("Invalid restriction key: " + key);
+ }
+ if (VERBOSE_LOG) {
+ Slogf.v(LOG_TAG, "Creating SystemEnforcingAdmin %s for calling package %s",
+ systemEntity, caller.getPackageName());
+ }
+ EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+
+ logUserRestrictionCall(key, enabled, /* parent= */ false, caller, UserHandle.USER_ALL);
+ }
+
private void setLocalUserRestrictionInternal(
EnforcingAdmin admin, String key, boolean enabled, int userId) {
PolicyDefinition<Boolean> policyDefinition =
@@ -13769,6 +13611,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
userId);
}
}
+
private void setGlobalUserRestrictionInternal(
EnforcingAdmin admin, String key, boolean enabled) {
PolicyDefinition<Boolean> policyDefinition =
@@ -14190,8 +14033,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
List<ResolveInfo> activitiesToEnable = mIPackageManager
.queryIntentActivities(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE,
parentUserId)
.getList();
@@ -14810,34 +14653,76 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private boolean hasActiveSupervisionTestAdminLocked(@UserIdInt int userId) {
+ ensureLocked();
+ if (mConstants.USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT) {
+ final DevicePolicyData policy = getUserData(userId);
+ for (ActiveAdmin admin : policy.mAdminMap.values()) {
+ if (admin != null && admin.testOnlyAdmin) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@Override
public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled,
PersistableBundle options) {
- Objects.requireNonNull(who, "ComponentName is null");
-
- // Check can set secondary lockscreen enabled
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwner(caller));
- Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
- "User %d is not allowed to call setSecondaryLockscreenEnabled",
+ if (Flags.secondaryLockscreenApiEnabled()) {
+ final CallerIdentity caller = getCallerIdentity();
+ final boolean isRoleHolder = isCallerSystemSupervisionRoleHolder(caller);
+ synchronized (getLockObject()) {
+ // TODO(b/378102594): Remove access for test admins.
+ final boolean isTestAdmin = hasActiveSupervisionTestAdminLocked(caller.getUserId());
+ Preconditions.checkCallAuthorization(isRoleHolder || isTestAdmin,
+ "Caller (%d) is not the SYSTEM_SUPERVISION role holder",
caller.getUserId());
+ }
- synchronized (getLockObject()) {
- // Allow testOnly admins to bypass supervision config requirement.
- Preconditions.checkCallAuthorization(isAdminTestOnlyLocked(who, caller.getUserId())
- || isSupervisionComponentLocked(caller.getComponentName()), "Admin %s is not "
- + "the default supervision component", caller.getComponentName());
- DevicePolicyData policy = getUserData(caller.getUserId());
- policy.mSecondaryLockscreenEnabled = enabled;
- saveSettingsLocked(caller.getUserId());
+ if (mSupervisionManagerInternal != null) {
+ mSupervisionManagerInternal.setSupervisionLockscreenEnabledForUser(
+ caller.getUserId(), enabled, options);
+ } else {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(caller.getUserId());
+ policy.mSecondaryLockscreenEnabled = enabled;
+ saveSettingsLocked(caller.getUserId());
+ }
+ }
+ } else {
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ // Check can set secondary lockscreen enabled
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
+ "User %d is not allowed to call setSecondaryLockscreenEnabled",
+ caller.getUserId());
+
+ synchronized (getLockObject()) {
+ // Allow testOnly admins to bypass supervision config requirement.
+ Preconditions.checkCallAuthorization(isAdminTestOnlyLocked(who, caller.getUserId())
+ || isSupervisionComponentLocked(caller.getComponentName()),
+ "Admin %s is not the default supervision component",
+ caller.getComponentName());
+ DevicePolicyData policy = getUserData(caller.getUserId());
+ policy.mSecondaryLockscreenEnabled = enabled;
+ saveSettingsLocked(caller.getUserId());
+ }
}
}
@Override
public boolean isSecondaryLockscreenEnabled(@NonNull UserHandle userHandle) {
- synchronized (getLockObject()) {
- return getUserData(userHandle.getIdentifier()).mSecondaryLockscreenEnabled;
+ if (Flags.secondaryLockscreenApiEnabled() && mSupervisionManagerInternal != null) {
+ return mSupervisionManagerInternal.isSupervisionLockscreenEnabledForUser(
+ userHandle.getIdentifier());
+ } else {
+ synchronized (getLockObject()) {
+ return getUserData(userHandle.getIdentifier()).mSecondaryLockscreenEnabled;
+ }
}
}
@@ -15036,7 +14921,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (policy == null) {
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
- return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ return LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
}
return policy.getFlags();
}
@@ -15165,7 +15050,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"Permission denial: device owners cannot update %1$s", setting));
}
- if (Settings.Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
+ if (Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
// ignore if it contradicts an existing policy
long timeMs = getMaximumTimeToLock(
who, mInjector.userHandleGetCallingUserId(), /* parent */ false);
@@ -15213,19 +15098,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -15244,16 +15122,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
CallerIdentity caller = getCallerIdentity(who);
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- Preconditions.checkNotNull(who, "ComponentName is null");
-
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -15341,18 +15213,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- // This is a global action.
- enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -15369,18 +15234,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName,
String timeZone) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- // This is a global action.
- enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -15585,12 +15443,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public boolean setStatusBarDisabled(ComponentName who, String callerPackageName,
boolean disabled) {
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (isSetStatusBarDisabledCoexistenceEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isUnicornFlagEnabled()) {
+ if (isSetStatusBarDisabledCoexistenceEnabled()) {
enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
@@ -15601,7 +15459,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int userId = caller.getUserId();
synchronized (getLockObject()) {
- if (!isUnicornFlagEnabled()) {
+ if (!isSetStatusBarDisabledCoexistenceEnabled()) {
Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
"Admin " + who + " is neither the device owner or affiliated "
+ "user's profile owner.");
@@ -15660,7 +15518,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isStatusBarDisabled(String callerPackage) {
final CallerIdentity caller = getCallerIdentity(callerPackage);
- if (isUnicornFlagEnabled()) {
+ if (isSetStatusBarDisabledCoexistenceEnabled()) {
enforceCanQuery(
MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId());
} else {
@@ -15670,7 +15528,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int userId = caller.getUserId();
synchronized (getLockObject()) {
- if (!isUnicornFlagEnabled()) {
+ if (!isSetStatusBarDisabledCoexistenceEnabled()) {
Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
"Admin " + callerPackage
+ " is neither the device owner or affiliated user's profile owner.");
@@ -15697,7 +15555,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = users.size();
for (int i = 0; i < N; i++) {
int userHandle = users.get(i).id;
- if (mInjector.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
+ if (mInjector.settingsSecureGetIntForUser(USER_SETUP_COMPLETE, 0,
userHandle) != 0) {
DevicePolicyData policy = getUserData(userHandle);
if (!policy.mUserSetupComplete) {
@@ -15725,7 +15583,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private class SetupContentObserver extends ContentObserver {
private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
- Settings.Secure.USER_SETUP_COMPLETE);
+ USER_SETUP_COMPLETE);
private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
private final Uri mDefaultImeChanged = Settings.Secure.getUriFor(
Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -15773,7 +15631,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private class DevicePolicyConstantsObserver extends ContentObserver {
final Uri mConstantsUri =
- Settings.Global.getUriFor(Settings.Global.DEVICE_POLICY_CONSTANTS);
+ Global.getUriFor(Global.DEVICE_POLICY_CONSTANTS);
DevicePolicyConstantsObserver(Handler handler) {
super(handler);
@@ -16066,9 +15924,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int uid = Objects.requireNonNull(
mInjector.getPackageManager().getApplicationInfoAsUser(
Objects.requireNonNull(packageName), /* flags= */ 0, userId)).uid;
- return PackageManager.PERMISSION_GRANTED
+ return PERMISSION_GRANTED
== ActivityManager.checkComponentPermission(
- android.Manifest.permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
+ permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
-1, /* exported= */ true);
} catch (NameNotFoundException ex) {
Slogf.w(LOG_TAG, "Cannot find the package %s to check for permissions.",
@@ -16205,7 +16063,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
- ? AppOpsManager.MODE_ALLOWED
+ ? MODE_ALLOWED
: AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
}
@@ -16390,6 +16248,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
admin.info.getComponent());
return result;
+ } else if (android.security.Flags.aapmApi()) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ return result;
}
return null;
} finally {
@@ -16399,6 +16261,54 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return null;
}
+ private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId,
+ String identifier) {
+ Objects.requireNonNull(identifier);
+
+ Set<EnforcingAdmin> admins = getEnforcingAdminsForIdentifier(userId, identifier);
+ if (admins.isEmpty()) {
+ return null;
+ }
+
+ final EnforcingAdmin admin;
+ if (admins.size() == 1) {
+ admin = admins.iterator().next();
+ } else {
+ Optional<EnforcingAdmin> dpc = admins.stream()
+ .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
+ admin = dpc.orElseGet(() -> admins.stream().findFirst().get());
+ }
+ return admin == null ? null : admin.getParcelableAdmin();
+ }
+
+ private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) {
+ // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as
+ // before the bug fix for b/192245204.
+ if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(identifier)) {
+ EnforcingAdmin admin = getProfileOrDeviceOwnerEnforcingAdmin(userId);
+ return admin == null ? Collections.emptySet() : Collections.singleton(admin);
+ }
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ final PolicyDefinition<V> policyDefinition = getPolicyDefinitionForIdentifier(
+ identifier);
+ V value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+ if (value == null) {
+ return Collections.emptySet();
+ }
+ return Stream.concat(mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policyDefinition)
+ .entrySet().stream(),
+ mDevicePolicyEngine.getLocalPoliciesSetByAdmins(policyDefinition,
+ userId).entrySet().stream())
+ .filter(entry -> value.equals(entry.getValue().getValue()))
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toSet());
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
/**
* @param restriction The restriction enforced by admin. It could be any user restriction or
* policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA},
@@ -16413,20 +16323,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// before the bug fix for b/192245204.
if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(
restriction)) {
- ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
- if (profileOwner != null) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- profileOwner, userId);
- admins.add(admin.getParcelableAdmin());
- return admins;
- }
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null && deviceOwner.first == userId) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- deviceOwner.second, deviceOwner.first);
+ EnforcingAdmin admin = getProfileOrDeviceOwnerEnforcingAdmin(userId);
+ if (admin != null) {
admins.add(admin.getParcelableAdmin());
- return admins;
}
} else {
long ident = mInjector.binderClearCallingIdentity();
@@ -16475,6 +16374,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private static <V> PolicyDefinition<V> getPolicyDefinitionForIdentifier(
+ @NonNull String identifier) {
+ Objects.requireNonNull(identifier);
+ if (Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY.equals(identifier)) {
+ return (PolicyDefinition<V>) PolicyDefinition.MEMORY_TAGGING;
+ } else {
+ return (PolicyDefinition<V>) getPolicyDefinitionForRestriction(identifier);
+ }
+ }
+
+ private EnforcingAdmin getProfileOrDeviceOwnerEnforcingAdmin(int userId) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner != null) {
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(profileOwner, userId);
+ }
+ final Pair<Integer, ComponentName> deviceOwner = mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null && deviceOwner.first == userId) {
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(deviceOwner.second,
+ deviceOwner.first);
+ }
+ return null;
+ }
+
private static String userRestrictionSourceToString(@UserRestrictionSource int source) {
return DebugUtils.flagsToString(UserManager.class, "RESTRICTION_", source);
}
@@ -16492,6 +16414,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public android.app.admin.EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ return getEnforcingAdminInternal(userId, identifier);
+ }
+
+ @Override
public List<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestriction(
int userId, String restriction) {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
@@ -16524,7 +16452,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
- return admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
+ || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
@Override
@@ -16584,22 +16513,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.validateAgainstPreviousFreezePeriod(record.first, record.second,
LocalDate.now());
}
- CallerIdentity caller;
- synchronized (getLockObject()) {
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
+ CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller)
|| isDefaultDeviceOwner(caller));
- }
- checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
+ synchronized (getLockObject()) {
if (policy == null) {
mOwners.clearSystemUpdatePolicy();
} else {
@@ -16746,7 +16668,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) {
Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast "
+ "update information.");
- return;
}
});
@@ -16770,7 +16691,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
// Get running users.
- final int runningUserIds[];
+ final int[] runningUserIds;
try {
runningUserIds = mInjector.getIActivityManager().getRunningUserIds();
} catch (RemoteException e) {
@@ -16862,7 +16783,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
EnforcingAdmin enforcingAdmin;
- if (isUnicornFlagEnabled()) {
+
+ // TODO(b/370472975): enable when we stop policy enforecer callback from blocking the main
+ // thread
+ if (Flags.setPermissionGrantStateCoexistence()) {
enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -16887,6 +16811,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
callback.sendResult(null);
return;
}
+
+ // TODO(b/266924257): decide how to handle the internal state if the package doesn't
+ // exist, or the permission isn't requested by the app, because we could end up with
+ // inconsistent state between the policy engine and package manager. Also a package
+ // might get removed or has it's permission updated after we've set the policy.
+ if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+ enforcingAdmin,
+ caller.getUserId());
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+ enforcingAdmin,
+ new IntegerPolicyValue(grantState),
+ caller.getUserId());
+ }
+ int newState = mInjector.binderWithCleanCallingIdentity(() ->
+ getPermissionGrantStateForUser(
+ packageName, permission, caller, caller.getUserId()));
+ if (newState == grantState) {
+ callback.sendResult(Bundle.EMPTY);
+ } else {
+ callback.sendResult(null);
+ }
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
@@ -16909,13 +16858,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
synchronized (getLockObject()) {
long ident = mInjector.binderClearCallingIdentity();
+ boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
+ >= Build.VERSION_CODES.Q;
+
try {
- boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
if (!isPostQAdmin) {
// Legacy admins assume that they cannot control pre-M apps
if (getTargetSdk(packageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ < Build.VERSION_CODES.M) {
callback.sendResult(null);
return;
}
@@ -16924,50 +16874,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
callback.sendResult(null);
return;
}
- } catch (SecurityException e) {
- Slogf.e(LOG_TAG, "Could not set permission grant state", e);
- callback.sendResult(null);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
- }
- }
- // TODO(b/278710449): enable when we stop policy enforecer callback from blocking the main
- // thread
- if (false) {
- // TODO(b/266924257): decide how to handle the internal state if the package doesn't
- // exist, or the permission isn't requested by the app, because we could end up with
- // inconsistent state between the policy engine and package manager. Also a package
- // might get removed or has it's permission updated after we've set the policy.
- if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
- mDevicePolicyEngine.removeLocalPolicy(
- PolicyDefinition.PERMISSION_GRANT(packageName, permission),
- enforcingAdmin,
- caller.getUserId());
- } else {
- mDevicePolicyEngine.setLocalPolicy(
- PolicyDefinition.PERMISSION_GRANT(packageName, permission),
- enforcingAdmin,
- new IntegerPolicyValue(grantState),
- caller.getUserId());
- }
- int newState = mInjector.binderWithCleanCallingIdentity(() ->
- getPermissionGrantStateForUser(
- packageName, permission, caller, caller.getUserId()));
- if (newState == grantState) {
- callback.sendResult(Bundle.EMPTY);
- } else {
- callback.sendResult(null);
- }
- } else {
- synchronized (getLockObject()) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
if (grantState == PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
- || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ || grantState == PERMISSION_GRANT_STATE_DEFAULT) {
AdminPermissionControlParams permissionParams =
new AdminPermissionControlParams(packageName, permission,
grantState,
@@ -16986,7 +16895,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
} catch (SecurityException e) {
Slogf.e(LOG_TAG, "Could not set permission grant state", e);
-
callback.sendResult(null);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -17003,33 +16911,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>();
{
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.CAMERA);
- SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
- SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
- SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
- SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_FINE_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_BACKGROUND_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_COARSE_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.CAMERA);
+ SENSOR_PERMISSIONS.add(permission.RECORD_AUDIO);
+ SENSOR_PERMISSIONS.add(permission.ACTIVITY_RECOGNITION);
+ SENSOR_PERMISSIONS.add(permission.BODY_SENSORS);
+ SENSOR_PERMISSIONS.add(permission.BACKGROUND_CAMERA);
+ SENSOR_PERMISSIONS.add(permission.RECORD_BACKGROUND_AUDIO);
+ SENSOR_PERMISSIONS.add(permission.BODY_SENSORS_BACKGROUND);
}
private boolean canGrantPermission(CallerIdentity caller, String permission,
String targetPackageName) {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
+ >= Build.VERSION_CODES.Q;
if (!isPostQAdmin) {
// Legacy admins assume that they cannot control pre-M apps
if (getTargetSdk(targetPackageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ < Build.VERSION_CODES.M) {
return false;
}
}
- if (!isRuntimePermission(permission)) {
- return false;
- }
- return true;
+ return isRuntimePermission(permission);
}
private void enforcePermissionGrantStateOnFinancedDevice(
@@ -17047,7 +16952,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public int getPermissionGrantState(ComponentName admin, String callerPackage,
String packageName, String permission) throws RemoteException {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- if (isUnicornFlagEnabled()) {
+ if (Flags.setPermissionGrantStateCoexistence()) {
enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(),
caller.getUserId());
} else {
@@ -17072,7 +16977,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throws RemoteException {
int granted;
if (getTargetSdk(caller.getPackageName(), caller.getUserId())
- < android.os.Build.VERSION_CODES.Q) {
+ < Build.VERSION_CODES.Q) {
// The per-Q behavior was to not check the app-ops state.
granted = mIPackageManager.checkPermission(permission, packageName, userId);
} else {
@@ -17081,11 +16986,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (packageState == null) {
Slog.w(LOG_TAG, "Can't get permission state for missing package "
+ packageName);
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
Slog.w(LOG_TAG, "Can't get permission state for uninstalled package "
+ packageName);
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else {
if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
PermissionChecker.PID_UNKNOWN,
@@ -17093,7 +16998,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
!= PermissionChecker.PERMISSION_GRANTED) {
granted = PackageManager.PERMISSION_DENIED;
} else {
- granted = PackageManager.PERMISSION_GRANTED;
+ granted = PERMISSION_GRANTED;
}
}
@@ -17104,11 +17009,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED)
!= PackageManager.FLAG_PERMISSION_POLICY_FIXED) {
// Not controlled by policy
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else {
// Policy controlled so return result based on permission grant state
- return granted == PackageManager.PERMISSION_GRANTED
- ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+ return granted == PERMISSION_GRANTED
+ ? PERMISSION_GRANT_STATE_GRANTED
: DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
}
}
@@ -17228,9 +17133,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (action != null) {
switch (action) {
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
+ case ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, userId);
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+ case ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(componentName, userId);
}
@@ -17444,18 +17349,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public String getWifiMacAddress(ComponentName admin, String callerPackageName) {
-// if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(admin, "ComponentName is null");
-// }
+ Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
-// if (isPermissionCheckFlagEnabled()) {
-// enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL);
-// } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
-// }
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -17522,25 +17421,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- CallerIdentity caller;
- ActiveAdmin admin;
message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH);
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- synchronized (getLockObject()) {
- admin = getActiveAdminForUidLocked(who, caller.getUid());
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ admin = getActiveAdminForUidLocked(who, caller.getUid());
}
synchronized (getLockObject()) {
@@ -17561,23 +17450,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return null;
}
- CallerIdentity caller;
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- synchronized (getLockObject()) {
- admin = getActiveAdminForUidLocked(who, caller.getUid());
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ admin = getActiveAdminForUidLocked(who, caller.getUid());
}
return admin.shortSupportMessage;
}
@@ -17740,26 +17619,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
CallerIdentity caller = getCallerIdentity(who);
- ActiveAdmin admin = null;
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH);
synchronized (getLockObject()) {
- if (!isPermissionCheckFlagEnabled()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
if (!TextUtils.equals(admin.organizationName, text)) {
admin.organizationName = (text == null || text.length() == 0)
? null : text.toString();
@@ -17774,23 +17641,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return null;
}
CallerIdentity caller = getCallerIdentity(who);
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- synchronized (getLockObject()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
}
return admin.organizationName;
@@ -18177,6 +18035,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
+ @GuardedBy("getLockObject()")
private boolean areAllUsersAffiliatedWithDeviceLocked() {
return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mUserManager.getAliveUsers();
@@ -18273,22 +18132,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
- enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
- UserHandle.USER_ALL);
+ if (admin != null) {
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isDefaultDeviceOwner(caller));
} else {
- if (admin != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
+ // A delegate app passes a null admin component, which is expected
+ Preconditions.checkCallAuthorization(
+ isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
+ }
+ synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
|| areAllUsersAffiliatedWithDeviceLocked());
}
@@ -18314,7 +18168,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return new ParceledListSlice<SecurityEvent>(output);
} catch (IOException e) {
Slogf.w(LOG_TAG, "Fail to read previous events" , e);
- return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList());
+ return new ParceledListSlice<SecurityEvent>(Collections.emptyList());
}
}
@@ -18628,7 +18482,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
boolean isUserCompleted = mInjector.settingsSecureGetIntForUser(
- Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
+ USER_SETUP_COMPLETE, 0, userId) != 0;
DevicePolicyData policy = getUserData(userId);
policy.mUserSetupComplete = isUserCompleted;
mStateCache.setDeviceProvisioned(isUserCompleted);
@@ -18807,8 +18661,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean hasIncompatibleAccounts(int userId) {
- return mHasIncompatibleAccounts == null ? true
- : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false);
+ return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault(
+ userId, /* default= */ false);
}
/**
@@ -18925,7 +18779,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
}
- };
+ }
private boolean isAdb(CallerIdentity caller) {
return isShellUid(caller) || isRootUid(caller);
@@ -19331,14 +19185,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new IllegalArgumentException("token must be at least 32-byte long");
}
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
}
final int userId = caller.getUserId();
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19351,6 +19205,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
long tokenHandle = addEscrowToken(
token, currentTokenHandle == null ? 0 : currentTokenHandle, userId);
if (tokenHandle == 0) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.RESET_PASSWORD_TOKEN,
+ enforcingAdmin,
+ userId);
return false;
}
mDevicePolicyEngine.setLocalPolicy(
@@ -19394,7 +19252,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
@@ -19402,7 +19260,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int userId = caller.getUserId();
boolean result = false;
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19441,14 +19299,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
}
int userId = caller.getUserId();
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19471,13 +19329,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private boolean isAnyResetPasswordTokenActiveForUserLocked(int userId) {
+ return mDevicePolicyEngine
+ .getLocalPoliciesSetByAdmins(PolicyDefinition.RESET_PASSWORD_TOKEN, userId)
+ .entrySet()
+ .stream()
+ .anyMatch((e) -> {
+ EnforcingAdmin admin = e.getKey();
+ PolicyValue<Long> policyValue = e.getValue();
+ return isResetPasswordTokenActiveForUserLocked(policyValue.getValue(), userId)
+ && isEncryptionAware(admin.getPackageName(), userId);
+ });
+ }
+
private boolean isResetPasswordTokenActiveForUserLocked(
long passwordTokenHandle, int userHandle) {
- if (passwordTokenHandle != 0) {
- return mInjector.binderWithCleanCallingIdentity(() ->
+ return passwordTokenHandle != 0 && mInjector.binderWithCleanCallingIdentity(() ->
mLockPatternUtils.isEscrowTokenActive(passwordTokenHandle, userHandle));
- }
- return false;
}
@Override
@@ -19490,7 +19358,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Objects.requireNonNull(token);
CallerIdentity caller;
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
@@ -19500,7 +19368,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
boolean result = false;
final String password = passwordOrNull != null ? passwordOrNull : "";
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19531,7 +19399,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (result) {
- if (isUnicornFlagEnabled()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
.setAdmin(callerPackageName)
@@ -20219,21 +20087,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void installUpdateFromFile(ComponentName admin, String callerPackageName,
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(admin, "ComponentName is null");
- }
+ Objects.requireNonNull(admin, "ComponentName is null");
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
DevicePolicyEventLogger
@@ -20256,7 +20115,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean isDeviceAB() {
- return "true".equalsIgnoreCase(android.os.SystemProperties
+ return "true".equalsIgnoreCase(SystemProperties
.get(AB_DEVICE_KEY, ""));
}
@@ -20523,7 +20382,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mOwners.hasDeviceOwner()
&& mInjector.getIActivityManager().getLockTaskModeState()
== ActivityManager.LOCK_TASK_MODE_LOCKED
- && !isLockTaskFeatureEnabled(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO)
+ && !isLockTaskFeatureEnabled(LOCK_TASK_FEATURE_SYSTEM_INFO)
&& !deviceHasKeyguard()
&& !inEphemeralUserSession();
}
@@ -20534,7 +20393,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int lockTaskFeatures = policy == null
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
- ? DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ ? LOCK_TASK_FEATURE_GLOBAL_ACTIONS
: policy.getFlags();
return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
}
@@ -20803,32 +20662,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName,
boolean enabled) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
- final ActiveAdmin admin;
+ CallerIdentity caller = getCallerIdentity(who);
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "Common Criteria mode can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
- synchronized (getLockObject()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "Common Criteria mode can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
admin.mCommonCriteriaMode = enabled;
saveSettingsLocked(caller.getUserId());
}
@@ -20860,7 +20702,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// their ActiveAdmin, instead of iterating through all admins.
ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- return admin != null ? admin.mCommonCriteriaMode : false;
+ return admin != null && admin.mCommonCriteriaMode;
}
}
@@ -21297,7 +21139,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private boolean canHandleCheckPolicyComplianceIntent(CallerIdentity caller) {
mInjector.binderWithCleanCallingIdentity(() -> {
- final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+ final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
intent.setPackage(caller.getPackageName());
final List<ResolveInfo> handlers =
mInjector.getPackageManager().queryIntentActivitiesAsUser(intent, /* flags= */
@@ -21353,6 +21195,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
String.format(NOT_SYSTEM_CALLER_MSG,
"call canProfileOwnerResetPasswordWhenLocked"));
synchronized (getLockObject()) {
+ if (Flags.resetPasswordWithTokenCoexistence()) {
+ return isAnyResetPasswordTokenActiveForUserLocked(userId);
+ }
final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
DevicePolicyData policy = getUserData(userId);
if (poAdmin == null
@@ -21361,24 +21206,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mPasswordTokenHandle, userId)) {
return false;
}
- final ApplicationInfo poAppInfo;
- try {
- poAppInfo = mIPackageManager.getApplicationInfo(
- poAdmin.info.getPackageName(), 0 /* flags */, userId);
- } catch (RemoteException e) {
- Slogf.e(LOG_TAG, "Failed to query PO app info", e);
- return false;
- }
- if (poAppInfo == null) {
- Slogf.wtf(LOG_TAG, "Cannot find AppInfo for profile owner");
- return false;
- }
- if (!poAppInfo.isEncryptionAware()) {
- return false;
- }
- Slogf.d(LOG_TAG, "PO should be able to reset password from direct boot");
- return true;
+ return isEncryptionAware(poAdmin.info.getPackageName(), userId);
+ }
+ }
+
+ private boolean isEncryptionAware(String packageName, int userId) {
+ final ApplicationInfo poAppInfo;
+ try {
+ poAppInfo = mIPackageManager.getApplicationInfo(packageName, 0 /* flags */, userId);
+ } catch (RemoteException e) {
+ Slogf.e(LOG_TAG, "Failed to query PO / role holder's app info", e);
+ return false;
+ }
+ if (poAppInfo == null) {
+ Slogf.wtf(LOG_TAG, "Cannot find AppInfo for PO / role holder");
+ return false;
}
+ if (!poAppInfo.isEncryptionAware()) {
+ return false;
+ }
+ Slogf.d(LOG_TAG, "PO / role holder should be able to reset password from direct boot");
+ return true;
}
@Override
@@ -21469,6 +21317,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public boolean removeManagedProfile(int userId) {
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ if (!isManagedProfile(userId)){
+ throw new IllegalArgumentException("Cannot remove user as it is not a managed profile");
+ }
+
+ boolean success = false;
+ final long identity = Binder.clearCallingIdentity();
+ try{
+ success = mUserManager.removeUserEvenWhenDisallowed(userId);
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, "Remove managed profile failed due to: ", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return success;
+ }
+
+ @Override
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams,
@NonNull String callerPackage) {
@@ -21482,6 +21351,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ if (Flags.splitCreateManagedProfileEnabled()) {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ UserHandle managedProfileUser =
+ createManagedProfileInternal(provisioningParams, caller);
+ maybeMigrateAccount(managedProfileUser.getIdentifier(), caller.getUserId(),
+ provisioningParams.getAccountToMigrate(),
+ provisioningParams.isKeepingAccountOnMigration(), callerPackage);
+ finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+ return managedProfileUser;
+ });
+ }
provisioningParams.logParams(callerPackage);
UserInfo userInfo = null;
@@ -21575,6 +21455,130 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public UserHandle createManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull String callerPackage) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(callerPackage, "callerPackage is null");
+ Objects.requireNonNull(provisioningParams.getProfileAdminComponentName(), "admin is null");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ CallerIdentity caller = getCallerIdentity(callerPackage);
+
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ createManagedProfileInternal(provisioningParams, caller));
+ }
+
+ private UserHandle createManagedProfileInternal(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull CallerIdentity caller) {
+ provisioningParams.logParams(caller.getPackageName());
+ final ComponentName admin = provisioningParams.getProfileAdminComponentName();
+ final int callingUserId = caller.getUserId();
+ UserInfo userInfo = null;
+ try {
+ final int result = checkProvisioningPreconditionSkipPermission(
+ ACTION_PROVISION_MANAGED_PROFILE, admin, callingUserId);
+ if (result != STATUS_OK) {
+ throw new ServiceSpecificException(
+ ERROR_PRE_CONDITION_FAILED,
+ "Provisioning preconditions failed with result: " + result);
+ }
+
+ final long startTime = SystemClock.elapsedRealtime();
+
+ onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
+ userInfo = createProfileForUser(provisioningParams, callingUserId);
+ if (userInfo == null) {
+ throw new ServiceSpecificException(
+ ERROR_PROFILE_CREATION_FAILED,
+ "Error creating profile, createProfileForUserEvenWhenDisallowed "
+ + "returned null.");
+ }
+ resetInteractAcrossProfilesAppOps(caller.getUserId());
+ logEventDuration(
+ DevicePolicyEnums.PLATFORM_PROVISIONING_CREATE_PROFILE_MS,
+ startTime,
+ caller.getPackageName());
+
+ maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
+ installExistingAdminPackage(userInfo.id, admin.getPackageName());
+
+ if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) {
+ throw new ServiceSpecificException(
+ ERROR_SETTING_PROFILE_OWNER_FAILED,
+ "Error setting profile owner.");
+ }
+ setUserSetupComplete(userInfo.id);
+ startProfileForSetup(userInfo.id, caller.getPackageName());
+
+ if (provisioningParams.isOrganizationOwnedProvisioning()) {
+ synchronized (getLockObject()) {
+ setProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, userInfo.id,
+ true);
+ }
+ }
+ return userInfo.getUserHandle();
+ } catch (Exception e) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+ .setStrings(caller.getPackageName())
+ .write();
+ // In case of any errors during provisioning, remove the newly created profile.
+ if (userInfo != null) {
+ mUserManager.removeUserEvenWhenDisallowed(userInfo.id);
+ }
+ throw e;
+ }
+ }
+
+ private UserInfo createProfileForUser(ManagedProfileProvisioningParams params, int userId) {
+ final Set<String> nonRequiredApps = params.isLeaveAllSystemAppsEnabled()
+ ? Collections.emptySet()
+ : mOverlayPackagesProvider.getNonRequiredApps(params.getProfileAdminComponentName(),
+ userId, ACTION_PROVISION_MANAGED_PROFILE);
+ if (nonRequiredApps.isEmpty()) {
+ Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+ } else {
+ for (String packageName : nonRequiredApps) {
+ Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+ }
+ }
+ return mUserManager.createProfileForUserEvenWhenDisallowed(
+ params.getProfileName(),
+ UserManager.USER_TYPE_PROFILE_MANAGED,
+ UserInfo.FLAG_DISABLED,
+ userId,
+ nonRequiredApps.toArray(new String[nonRequiredApps.size()]));
+ }
+
+ @Override
+ public void finalizeCreateManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(managedProfileUser, "managedProfileUser is null");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+ });
+ }
+
+ private void finalizeCreateManagedProfileInternal(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser
+ ) {
+ onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+ sendProvisioningCompletedBroadcast(
+ managedProfileUser.getIdentifier(),
+ ACTION_PROVISION_MANAGED_PROFILE,
+ provisioningParams.isLeaveAllSystemAppsEnabled());
+ }
+
+ @Override
public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser,
Account migratedAccount) {
Preconditions.checkCallAuthorization(
@@ -21744,7 +21748,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void pregrantDefaultInteractAcrossProfilesAppOps(@UserIdInt int userId) {
final String op =
- AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
+ AppOpsManager.permissionToOp(permission.INTERACT_ACROSS_PROFILES);
for (String packageName : getConfigurableDefaultCrossProfilePackages(userId)) {
if (!appOpIsDefaultOrAllowed(userId, op, packageName)) {
continue;
@@ -21947,7 +21951,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.i(LOG_TAG, "Account removed from the primary user.");
} else {
// TODO(174768447): Revisit start activity logic.
- final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class);
+ final Intent removeIntent =
+ result.getParcelable(AccountManager.KEY_INTENT, Intent.class);
removeIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
if (removeIntent != null) {
Slogf.i(LOG_TAG, "Starting activity to remove account");
@@ -22243,7 +22248,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
synchronized (getLockObject()) {
mInjector.settingsGlobalPutStringForUser(
- Settings.Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
+ Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
}
setUserProvisioningState(STATE_USER_SETUP_FINALIZED, userId);
@@ -22260,7 +22265,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else {
owner = getDeviceOrProfileOwnerAdminLocked(userId);
}
- boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+ boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions;
mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant);
}
}
@@ -22459,27 +22464,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(callerPackageName);
- } else {
- caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "Wi-Fi minimum security level can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
- }
+ CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "Wi-Fi minimum security level can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
boolean valueChanged = false;
synchronized (getLockObject()) {
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null,
- MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId())
- .getActiveAdmin();
- } else {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
if (admin.mWifiMinimumSecurityLevel != level) {
admin.mWifiMinimumSecurityLevel = level;
saveSettingsLocked(caller.getUserId());
@@ -22501,21 +22494,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity();
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName,
- caller.getUserId());
- } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || canQueryAdminPolicy(caller),
- "SSID policy can only be retrieved by a device owner or "
- + "a profile owner on an organization-owned device or "
- + "an app with the QUERY_ADMIN_POLICY permission.");
- }
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || canQueryAdminPolicy(caller),
+ "SSID policy can only be retrieved by a device owner or "
+ + "a profile owner on an organization-owned device or "
+ + "an app with the QUERY_ADMIN_POLICY permission.");
synchronized (getLockObject()) {
ActiveAdmin admin;
- admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
return admin != null ? admin.mWifiSsidPolicy : null;
}
}
@@ -22523,7 +22511,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isDevicePotentiallyStolen(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) {
+ if (!Flags.deviceTheftImplEnabled()) {
return false;
}
enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(),
@@ -22536,29 +22524,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) {
- CallerIdentity caller;
-
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(callerPackageName);
- } else {
- caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "SSID denylist can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
- }
+ CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "SSID denylist can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
boolean changed = false;
synchronized (getLockObject()) {
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(
- /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI,
- caller.getPackageName(),
- caller.getUserId()).getActiveAdmin();
- } else {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
if (!Objects.equals(policy, admin.mWifiSsidPolicy)) {
admin.mWifiSsidPolicy = policy;
changed = true;
@@ -22573,7 +22547,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(drawables, "drawables must be provided.");
@@ -22589,7 +22563,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void resetDrawables(@NonNull List<String> drawableIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(drawableIds, "drawableIds must be provided.");
@@ -22615,7 +22589,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setStrings(@NonNull List<DevicePolicyStringResource> strings) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(strings, "strings must be provided.");
@@ -22630,7 +22604,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void resetStrings(@NonNull List<String> stringIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
mInjector.binderWithCleanCallingIdentity(() -> {
if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) {
@@ -22700,7 +22674,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ permission.MANAGE_ROLE_HOLDERS));
setBypassDevicePolicyManagementRoleQualificationStateInternal(
/* currentRoleHolder= */ null, /* allowBypass= */ false);
}
@@ -22708,7 +22682,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ permission.MANAGE_ROLE_HOLDERS));
return mInjector.binderWithCleanCallingIdentity(() -> {
if (getUserData(
UserHandle.USER_SYSTEM).mBypassDevicePolicyManagementRoleQualifications) {
@@ -22766,7 +22740,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener {
- private RoleManager mRm;
+ private final RoleManager mRm;
private final Executor mExecutor;
private final Context mContext;
@@ -22783,13 +22757,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier());
- if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
- handleDevicePolicyManagementRoleChange(user);
- return;
- }
- if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
- handleFinancedDeviceKioskRoleChange();
- return;
+ switch (roleName) {
+ case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT ->
+ handleDevicePolicyManagementRoleChange(user);
+ case RoleManager.ROLE_FINANCED_DEVICE_KIOSK ->
+ handleFinancedDeviceKioskRoleChange();
}
}
@@ -23005,6 +22977,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
MANAGE_DEVICE_POLICY_AUTOFILL,
@@ -23092,6 +23065,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
MANAGE_DEVICE_POLICY_AUTOFILL,
@@ -23290,6 +23264,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_KEYGUARD,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ if (Flags.lockNowCoexistence()) {
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ }
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
@@ -23337,6 +23315,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
// These permissions may grant access to user data and therefore must be protected with
// MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls.
@@ -23364,8 +23344,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ if (!Flags.lockNowCoexistence()) {
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ }
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS,
@@ -23441,26 +23423,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Checks if the calling process has been granted permission to apply a device policy on a
- * specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- * Returns an {@link EnforcingAdmin} for the caller.
- *
- * @param admin the component name of the admin.
- * @param callerPackageName The package name of the calling application.
- * @param permission The name of the permission being checked.
- * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
- String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) {
- enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId);
- return getEnforcingAdminForCaller(admin, callerPackageName);
- }
-
- /**
- * Checks if the calling process has been granted permission to apply a device policy on a
* specific user. Only one permission provided in the list needs to be granted to pass this
* check.
* The given permissions will be checked along with their associated cross-user permissions if
@@ -23482,23 +23444,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
/**
- * Checks whether the calling process has been granted permission to query a device policy on
- * a specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- *
- * @param permission The name of the permission being checked.
- * @param targetUserId The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin,
- String permission, String callerPackageName, int targetUserId) {
- enforceCanQuery(permission, callerPackageName, targetUserId);
- return getEnforcingAdminForCaller(admin, callerPackageName);
- }
-
- /**
* Checks if the calling process has been granted permission to apply a device policy.
*
* @param callerPackageName The package name of the calling application.
@@ -23743,7 +23688,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
admin);
}
- admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+ admin = Flags.activeAdminCleanup()
+ ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
}
@@ -23766,8 +23712,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
- admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+ admin = Flags.activeAdminCleanup()
+ ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
return EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
}
@@ -23805,14 +23751,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return NOT_A_DPC;
}
- private boolean isPermissionCheckFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG,
- DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
- }
-
- static boolean isUnicornFlagEnabled() {
+ private static boolean isSetStatusBarDisabledCoexistenceEnabled() {
return false;
}
@@ -23863,24 +23802,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
public void setMtePolicy(int flags, String callerPackageName) {
- final Set<Integer> allowedModes =
- Set.of(
- DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
- DevicePolicyManager.MTE_DISABLED,
- DevicePolicyManager.MTE_ENABLED);
- Preconditions.checkArgument(
- allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
- // In general, this API should be available when "bootctl_settings_toggle" is set, which
- // signals that there is a control for MTE in the user settings and this API fundamentally
- // is a way for the device admin to override that setting.
- // Allow bootctl_device_policy_manager as an override, e.g. to offer the
- // DevicePolicyManager only without a visible user setting.
- if (!mInjector.systemPropertiesGetBoolean(
- "ro.arm64.memtag.bootctl_device_policy_manager",
- mInjector.systemPropertiesGetBoolean(
- "ro.arm64.memtag.bootctl_settings_toggle", false))) {
- throw new UnsupportedOperationException("device does not support MTE");
- }
+ checkMteSupportedAndAllowedPolicy(flags);
final CallerIdentity caller = getCallerIdentity(callerPackageName);
// For now we continue to restrict the DISABLED setting to device owner - we might need
// another permission for this in future.
@@ -23888,58 +23810,130 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
}
- if (isPermissionCheckFlagEnabled()) {
+ if (Flags.setMtePolicyCoexistence()) {
enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
}
+
synchronized (getLockObject()) {
- ActiveAdmin admin =
+ if (Flags.setMtePolicyCoexistence()) {
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin,
+ new IntegerPolicyValue(flags));
+ } else {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin);
+ }
+ } else {
+ ActiveAdmin admin =
getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-
- if (admin != null) {
- final String memtagProperty = "arm64.memtag.bootctl";
- if (flags == DevicePolicyManager.MTE_ENABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag");
- } else if (flags == DevicePolicyManager.MTE_DISABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
- } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- mInjector.systemPropertiesSet(memtagProperty, "default");
+ if (admin != null) {
+ final String memtagProperty = "arm64.memtag.bootctl";
+ if (flags == DevicePolicyManager.MTE_ENABLED) {
+ mInjector.systemPropertiesSet(memtagProperty, "memtag");
+ } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+ mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+ } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mInjector.systemPropertiesSet(memtagProperty, "default");
+ }
}
+ admin.mtePolicy = flags;
+ saveSettingsLocked(caller.getUserId());
}
- admin.mtePolicy = flags;
- saveSettingsLocked(caller.getUserId());
-
- DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
- .setInt(flags)
- .setAdmin(caller.getPackageName())
- .write();
}
+
+ DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
+ .setInt(flags)
+ .setAdmin(caller.getPackageName())
+ .write();
+ }
+ }
+
+ @Override
+ public void setMtePolicyBySystem(
+ @NonNull String systemEntity, int policy) {
+ Objects.requireNonNull(systemEntity);
+ checkMteSupportedAndAllowedPolicy(policy);
+
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
+ "Only system services can call setMtePolicyBySystem");
+
+ if (!Flags.setMtePolicyCoexistence()) {
+ throw new UnsupportedOperationException("System can not set MTE policy only");
+ }
+
+ EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+ if (policy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin,
+ new IntegerPolicyValue(policy));
+ } else {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin);
+ }
+ }
+
+ private void checkMteSupportedAndAllowedPolicy(int policy) {
+ final Set<Integer> allowedModes =
+ Set.of(
+ DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+ DevicePolicyManager.MTE_DISABLED,
+ DevicePolicyManager.MTE_ENABLED);
+ Preconditions.checkArgument(
+ allowedModes.contains(policy), "Provided mode is not one of the allowed values.");
+ // In general, this API should be available when "bootctl_settings_toggle" is set, which
+ // signals that there is a control for MTE in the user settings and this API fundamentally
+ // is a way for the device admin to override that setting.
+ // Allow bootctl_device_policy_manager as an override, e.g. to offer the
+ // DevicePolicyManager only without a visible user setting.
+ if (!mInjector.systemPropertiesGetBoolean(
+ "ro.arm64.memtag.bootctl_device_policy_manager",
+ mInjector.systemPropertiesGetBoolean(
+ "ro.arm64.memtag.bootctl_settings_toggle", false))) {
+ throw new UnsupportedOperationException("device does not support MTE");
}
}
@Override
public int getMtePolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (isPermissionCheckFlagEnabled()) {
+ if (Flags.setMtePolicyCoexistence()) {
enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isSystemUid(caller));
}
+
synchronized (getLockObject()) {
- ActiveAdmin admin =
+ if (Flags.setMtePolicyCoexistence()) {
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.MEMORY_TAGGING, admin);
+ return (policyFromAdmin != null ? policyFromAdmin
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
+ } else {
+ ActiveAdmin admin =
getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- return admin != null
- ? admin.mtePolicy
- : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ return admin != null
+ ? admin.mtePolicy
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
}
}
@@ -23988,6 +23982,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public void setAppFunctionsPolicy(String callerPackageName, @AppFunctionsPolicy int policy) {
+ if (!android.app.appfunctions.flags.Flags.enableAppFunctionManager()) {
+ return;
+ }
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ int userId = caller.getUserId();
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APP_FUNCTIONS_POLICY);
+ EnforcingAdmin enforcingAdmin =
+ enforcePermissionAndGetEnforcingAdmin(
+ /* who */null, MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
+ callerPackageName, userId);
+
+ if (policy == APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.APP_FUNCTIONS, enforcingAdmin, userId);
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.APP_FUNCTIONS,
+ enforcingAdmin, new IntegerPolicyValue(policy),
+ userId);
+ }
+ }
+
+ @Override
+ public @AppFunctionsPolicy int getAppFunctionsPolicy(String callerPackageName, int userId) {
+ if (!android.app.appfunctions.flags.Flags.enableAppFunctionManager()) {
+ return APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforceCanQuery(MANAGE_DEVICE_POLICY_APP_FUNCTIONS, callerPackageName, userId);
+ Integer policy =
+ mDevicePolicyEngine.getResolvedPolicy(PolicyDefinition.APP_FUNCTIONS, userId);
+ if (policy == null) {
+ return APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
+ }
+ return policy;
+ }
+
private void updateContentProtectionPolicyCache(@UserIdInt int userId) {
mPolicyCache.setContentProtectionPolicy(
userId,
@@ -24255,8 +24290,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (getLockObject()) {
Slogf.i(LOG_TAG,
"Started device policies migration to the device policy engine.");
- if (isUnicornFlagEnabled()) {
- migrateAutoTimezonePolicy();
+ // TODO(b/359188869): Move this to the current migration method.
+ if (Flags.setPermissionGrantStateCoexistence()) {
migratePermissionGrantStatePolicies();
}
migratePermittedInputMethodsPolicyLocked();
@@ -24283,18 +24318,58 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
maybeMigrateSecurityLoggingPolicyLocked();
// ID format: <sdk-int>.<auto_increment_id>.<descriptions>'
String unmanagedBackupId = "35.1.unmanaged-mode";
- boolean migrated = false;
- migrated = migrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
- migrated = migrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId);
- if (migrated) {
+ boolean unmanagedMigrated = maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
+ if (unmanagedMigrated) {
Slogf.i(LOG_TAG, "Backup made: " + unmanagedBackupId);
}
+
+ String supervisionBackupId = "36.2.supervision-support";
+ boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId);
+ supervisionMigrated |= maybeMigrateSuspendedPackagesLocked(supervisionBackupId);
+ supervisionMigrated |= maybeMigrateSetKeyguardDisabledFeatures(supervisionBackupId);
+ if (supervisionMigrated) {
+ Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId);
+ }
+
+ String memoryTaggingBackupId = "36.3.memory-tagging";
+ boolean memoryTaggingMigrated = maybeMigrateMemoryTaggingLocked(memoryTaggingBackupId);
+ if (memoryTaggingMigrated) {
+ Slogf.i(LOG_TAG, "Backup made: " + memoryTaggingBackupId);
+ }
+
// Additional migration steps should repeat the pattern above with a new backupId.
}
- private void migrateAutoTimezonePolicy() {
- Slogf.i(LOG_TAG, "Skipping Migration of AUTO_TIMEZONE policy to device policy engine,"
- + "as no way to identify if the value was set by the admin or the user.");
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateSetKeyguardDisabledFeatures(String backupId) {
+ Slog.i(LOG_TAG, "Migrating set keyguard disabled features to policy engine");
+ if (!Flags.setKeyguardDisabledFeaturesCoexistence()) {
+ return false;
+ }
+ if (mOwners.isSetKeyguardDisabledFeaturesMigrated()) {
+ return false;
+ }
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ if (admin.disabledKeyguardFeatures == 0) {
+ return;
+ }
+ int userId = enforcingAdmin.getUserId();
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+ enforcingAdmin,
+ new IntegerPolicyValue(admin.disabledKeyguardFeatures),
+ userId);
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG, "Failed to migrate set keyguard disabled to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking set keyguard disabled features migration complete");
+ mOwners.markSetKeyguardDisabledFeaturesMigrated();
+ return true;
}
private void migratePermissionGrantStatePolicies() {
@@ -24314,7 +24389,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!isRuntimePermission(permission)) {
continue;
}
- int grantState = DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ int grantState = PERMISSION_GRANT_STATE_DEFAULT;
try {
grantState = getPermissionGrantStateForUser(
packageInfo.packageName, permission,
@@ -24327,7 +24402,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.e(LOG_TAG, e, "Error retrieving permission grant state for %s "
+ "and %s", packageInfo.packageName, permission);
}
- if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
// Not Controlled by a policy
continue;
}
@@ -24488,7 +24563,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
});
}
-
+
+ @GuardedBy("getLockObject()")
private void migrateUserControlDisabledPackagesLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
@@ -24704,7 +24780,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|| isCallerDevicePolicyManagementRoleHolder(caller)
|| isCallerSystemSupervisionRoleHolder(caller));
return getFinancedDeviceKioskRoleHolderOnAnyUser() != null;
- };
+ }
@Override
public String getFinancedDeviceKioskRoleHolder(String callerPackageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 634f1bc97772..5a0b079b6a24 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,6 +23,7 @@ import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
@@ -280,6 +281,10 @@ final class EnforcingAdmin {
return getAuthorities().contains(authority);
}
+ boolean isSystemAuthority() {
+ return mIsSystemAuthority;
+ }
+
@NonNull
String getPackageName() {
return mPackageName;
@@ -291,9 +296,17 @@ final class EnforcingAdmin {
@Nullable
public ActiveAdmin getActiveAdmin() {
+ if (Flags.activeAdminCleanup()) {
+ throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
+ }
return mActiveAdmin;
}
+ @Nullable
+ ComponentName getComponentName() {
+ return mComponentName;
+ }
+
@NonNull
android.app.admin.EnforcingAdmin getParcelableAdmin() {
Authority authority;
@@ -308,8 +321,10 @@ final class EnforcingAdmin {
authority = DpcAuthority.DPC_AUTHORITY;
} else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
+ } else if (mIsSystemAuthority) {
+ // For now, System Authority returns UnknownAuthority.
+ authority = new UnknownAuthority(mSystemEntity);
} else {
- // For now, System Authority returns UNKNOWN_AUTHORITY.
authority = UnknownAuthority.UNKNOWN_AUTHORITY;
}
return new android.app.admin.EnforcingAdmin(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3f9605ac2e5d..1c75f2f39c80 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -669,6 +669,45 @@ class Owners {
}
}
+ void markResetPasswordWithTokenMigrated() {
+ synchronized (mData) {
+ mData.mResetPasswordWithTokenMigrated = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
+ boolean isResetPasswordWithTokenMigrated() {
+ synchronized (mData) {
+ return mData.mResetPasswordWithTokenMigrated;
+ }
+ }
+
+ void markMemoryTaggingMigrated() {
+ synchronized (mData) {
+ mData.mMemoryTaggingMigrated = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
+ boolean isMemoryTaggingMigrated() {
+ synchronized (mData) {
+ return mData.mMemoryTaggingMigrated;
+ }
+ }
+
+ void markSetKeyguardDisabledFeaturesMigrated() {
+ synchronized (mData) {
+ mData.mSetKeyguardDisabledFeaturesMigrated = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
+ boolean isSetKeyguardDisabledFeaturesMigrated() {
+ synchronized (mData) {
+ return mData.mSetKeyguardDisabledFeaturesMigrated;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 87fd0024a0fa..caaf0964bb4e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -91,6 +91,13 @@ class OwnersData {
private static final String ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED =
"passwordComplexityMigrated";
private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated";
+ private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED =
+ "resetPasswordWithTokenMigrated";
+ private static final String ATTR_MEMORY_TAGGING_MIGRATED =
+ "memoryTaggingMigrated";
+ private static final String ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED =
+ "setKeyguardDisabledFeaturesMigrated";
+
private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
// Internal state for the device owner package.
@@ -122,6 +129,9 @@ class OwnersData {
boolean mSecurityLoggingMigrated = false;
boolean mRequiredPasswordComplexityMigrated = false;
boolean mSuspendedPackagesMigrated = false;
+ boolean mResetPasswordWithTokenMigrated = false;
+ boolean mMemoryTaggingMigrated = false;
+ boolean mSetKeyguardDisabledFeaturesMigrated = false;
boolean mPoliciesMigratedPostUpdate = false;
@@ -413,11 +423,24 @@ class OwnersData {
if (Flags.unmanagedModeMigration()) {
out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
mRequiredPasswordComplexityMigrated);
+ }
+ if (Flags.suspendPackagesCoexistence()) {
out.attributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED,
mSuspendedPackagesMigrated);
}
-
+ if (Flags.resetPasswordWithTokenCoexistence()) {
+ out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
+ mResetPasswordWithTokenMigrated);
+ }
+ if (Flags.setMtePolicyCoexistence()) {
+ out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+ mMemoryTaggingMigrated);
+ }
+ if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
+ out.attributeBoolean(null, ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED,
+ mSetKeyguardDisabledFeaturesMigrated);
+ }
out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
}
@@ -485,10 +508,19 @@ class OwnersData {
mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
&& parser.getAttributeBoolean(null,
ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
- mSuspendedPackagesMigrated = Flags.unmanagedModeMigration()
+ mSuspendedPackagesMigrated = Flags.suspendPackagesCoexistence()
&& parser.getAttributeBoolean(null,
ATTR_SUSPENDED_PACKAGES_MIGRATED, false);
-
+ mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
+ && parser.getAttributeBoolean(null,
+ ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
+ mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
+ && parser.getAttributeBoolean(null,
+ ATTR_MEMORY_TAGGING_MIGRATED, false);
+ mSetKeyguardDisabledFeaturesMigrated =
+ Flags.setKeyguardDisabledFeaturesCoexistence()
+ && parser.getAttributeBoolean(null,
+ ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED, false);
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
index 40cf0e979375..c8c953d53f9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
@@ -19,6 +19,7 @@ package com.android.server.devicepolicy;
import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.content.pm.PackageManagerInternal;
import android.util.ArraySet;
@@ -64,7 +65,7 @@ public class PackageSuspender {
/**
* Suspend packages that are requested by a single admin
*
- * @return a list of packages that the admin has requested to suspend but could not be
+ * @return an array of packages that the admin has requested to suspend but could not be
* suspended, due to DPM and PackageManager exemption list.
*
*/
@@ -87,7 +88,7 @@ public class PackageSuspender {
/**
* Suspend packages considering the exemption list.
*
- * @return the list of packages that couldn't be suspended, either due to the exemption list,
+ * @return the set of packages that couldn't be suspended, either due to the exemption list,
* or due to failures from PackageManagerInternal itself.
*/
private Set<String> suspendWithExemption(Set<String> packages) {
@@ -112,15 +113,15 @@ public class PackageSuspender {
/**
* Unsuspend packages that are requested by a single admin
*
- * @return a list of packages that the admin has requested to unsuspend but could not be
- * unsuspended, due to other amdin's policy or PackageManager restriction.
+ * @return an array of packages that the admin has requested to unsuspend but could not be
+ * unsuspended, due to other admin's policy or PackageManager restriction.
*
*/
public String[] unsuspend(Set<String> packages) {
- // Unlike suspend(), when unsuspending, call PackageManager with the delta of resolved
- // suspended packages list and not what the admin has requested. This is because some
- // packages might still be subject to another admin's suspension request.
- Set<String> packagesToUnsuspend = new ArraySet<>(mSuspendedPackageBefore);
+ // Unlike suspend(), when unsuspending, take suspension by other admins into account: only
+ // packages not suspended by other admins are passed to PackageManager.
+ Set<String> packagesToUnsuspend = new ArraySet<>(
+ Flags.unsuspendNotSuspended() ? packages : mSuspendedPackageBefore);
packagesToUnsuspend.removeAll(mSuspendedPackageAfter);
// To calculate the result (which packages are not unsuspended), start with packages that
@@ -139,7 +140,7 @@ public class PackageSuspender {
/**
* Unsuspend packages considering the exemption list.
*
- * @return the list of packages that couldn't be unsuspended, either due to the exemption list,
+ * @return the set of packages that couldn't be unsuspended, either due to the exemption list,
* or due to failures from PackageManagerInternal itself.
*/
private Set<String> unsuspendWithExemption(Set<String> packages) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f271162bbfa4..543e32fae55f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;
import static com.android.server.devicepolicy.DevicePolicyEngine.DEVICE_LOCK_CONTROLLER_ROLE;
+import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -53,6 +54,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
final class PolicyDefinition<V> {
@@ -93,14 +95,18 @@ final class PolicyDefinition<V> {
private static final MostRestrictive<Boolean> TRUE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(new BooleanPolicyValue(true), new BooleanPolicyValue(false)));
- static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
+ static PolicyDefinition<Integer> AUTO_TIME_ZONE = new PolicyDefinition<>(
new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY),
- // auto timezone is disabled by default, hence enabling it is more restrictive.
- TRUE_MORE_RESTRICTIVE,
+ // Auto time zone is enabled by default. Enabled state has higher priority given it
+ // means the time will be more precise and other applications can rely on that for
+ // their purposes.
+ new TopPriority<>(List.of(
+ EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+ EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+ EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_GLOBAL_ONLY_POLICY,
- (Boolean value, Context context, Integer userId, PolicyKey policyKey) ->
- PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
- new BooleanPolicySerializer());
+ PolicyEnforcerCallbacks::setAutoTimeZonePolicy,
+ new IntegerPolicySerializer());
static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
new PolicyDefinition<>(
@@ -312,6 +318,20 @@ final class PolicyDefinition<V> {
PolicyEnforcerCallbacks::setContentProtectionPolicy,
new IntegerPolicySerializer());
+ static PolicyDefinition<Integer> APP_FUNCTIONS = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.APP_FUNCTIONS_POLICY),
+ new MostRestrictive<>(
+ List.of(
+ new IntegerPolicyValue(
+ DevicePolicyManager.APP_FUNCTIONS_DISABLED),
+ new IntegerPolicyValue(
+ DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE),
+ new IntegerPolicyValue(
+ DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY))),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::noOp,
+ new IntegerPolicySerializer());
+
static PolicyDefinition<Integer> PASSWORD_COMPLEXITY = new PolicyDefinition<>(
new NoArgsPolicyKey(DevicePolicyIdentifiers.PASSWORD_COMPLEXITY_POLICY),
new MostRestrictive<>(
@@ -336,12 +356,29 @@ final class PolicyDefinition<V> {
PolicyEnforcerCallbacks::noOp,
new PackageSetPolicySerializer());
+ static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>(
+ new NoArgsPolicyKey(
+ DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY),
+ new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)),
+ PolicyEnforcerCallbacks::setMtePolicy,
+ new IntegerPolicySerializer());
+
+ static PolicyDefinition<Integer> AUTO_TIME = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIME_POLICY),
+ new TopPriority<>(List.of(
+ EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+ EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+ EnforcingAdmin.DPC_AUTHORITY)),
+ POLICY_FLAG_GLOBAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::setAutoTimePolicy,
+ new IntegerPolicySerializer());
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
// TODO(b/277218360): Revisit policies that should be marked as global-only.
static {
- POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIME_ZONE);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
GENERIC_PERMISSION_GRANT);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY,
@@ -375,6 +412,8 @@ final class PolicyDefinition<V> {
USB_DATA_SIGNALING);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY,
CONTENT_PROTECTION);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.APP_FUNCTIONS_POLICY,
+ APP_FUNCTIONS);
// Intentionally not flagged since if the flag is flipped off on a device already
// having PASSWORD_COMPLEXITY policy in the on-device XML, it will cause the
// deserialization logic to break due to seeing an unknown tag.
@@ -382,6 +421,9 @@ final class PolicyDefinition<V> {
PASSWORD_COMPLEXITY);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY,
PACKAGES_SUSPENDED);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
+ MEMORY_TAGGING);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIME_POLICY, AUTO_TIME);
// User Restriction Policies
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
@@ -504,7 +546,8 @@ final class PolicyDefinition<V> {
private final int mPolicyFlags;
// A function that accepts policy to apply, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
- private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
+ private final QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ mPolicyEnforcerCallback;
private final PolicySerializer<V> mPolicySerializer;
private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) {
@@ -574,7 +617,7 @@ final class PolicyDefinition<V> {
return mResolutionMechanism.resolve(adminsPolicy);
}
- boolean enforcePolicy(@Nullable V value, Context context, int userId) {
+ CompletableFuture<Boolean> enforcePolicy(@Nullable V value, Context context, int userId) {
return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
}
@@ -592,7 +635,6 @@ final class PolicyDefinition<V> {
POLICY_DEFINITIONS.put(key.getIdentifier(), definition);
}
-
/**
* Callers must ensure that {@code policyType} have implemented an appropriate
* {@link Object#equals} implementation.
@@ -600,7 +642,8 @@ final class PolicyDefinition<V> {
private PolicyDefinition(
@NonNull PolicyKey key,
ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
@@ -610,10 +653,11 @@ final class PolicyDefinition<V> {
* {@link Object#equals} and {@link Object#hashCode()} implementation.
*/
private PolicyDefinition(
- @NonNull PolicyKey policyKey,
+ @NonNull PolicyKey policyKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
Objects.requireNonNull(policyKey);
mPolicyKey = policyKey;
@@ -628,10 +672,6 @@ final class PolicyDefinition<V> {
}
}
- void saveToXml(TypedXmlSerializer serializer) throws IOException {
- mPolicyKey.saveToXml(serializer);
- }
-
@Nullable
static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 8068d46d6a9d..8f80004a7ea5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -47,6 +47,7 @@ import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.permission.AdminPermissionControlParams;
import android.permission.PermissionControllerManager;
@@ -55,6 +56,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
@@ -65,6 +67,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -73,33 +76,40 @@ final class PolicyEnforcerCallbacks {
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
- static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) {
- return true;
+ static <T> CompletableFuture<Boolean> noOp(T value, Context context, Integer userId,
+ PolicyKey policyKey) {
+ return AndroidFuture.completedFuture(true);
}
- static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
- if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
- Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
- return true;
+ static CompletableFuture<Boolean> setAutoTimeZonePolicy(
+ @Nullable Integer policy, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimeZonePolicy while flag is off.");
+ return AndroidFuture.completedFuture(true);
}
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
-
- int value = enabled != null && enabled ? 1 : 0;
- return Settings.Global.putInt(
+ if (policy != null &&
+ policy == DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY) {
+ return AndroidFuture.completedFuture(false);
+ }
+ int enabled = policy != null &&
+ policy == DevicePolicyManager.AUTO_TIME_ZONE_ENABLED ? 1 : 0;
+ return AndroidFuture.completedFuture(Settings.Global.putInt(
context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
- value);
+ enabled));
});
}
- static boolean setPermissionGrantState(
+ static CompletableFuture<Boolean> setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ if (!Flags.setPermissionGrantStateCoexistence()) {
Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
- return true;
+ return AndroidFuture.completedFuture(true);
}
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey);
@@ -125,12 +135,13 @@ final class PolicyEnforcerCallbacks {
.setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(),
permissionParams, context.getMainExecutor(), callback::trigger);
try {
- return callback.await(20_000, TimeUnit.MILLISECONDS);
+ return AndroidFuture.completedFuture(
+ callback.await(20_000, TimeUnit.MILLISECONDS));
} catch (Exception e) {
// TODO: add logging
- return false;
+ return AndroidFuture.completedFuture(false);
}
- }));
+ });
}
@NonNull
@@ -149,23 +160,23 @@ final class PolicyEnforcerCallbacks {
}
}
- static boolean enforceSecurityLogging(
+ static CompletableFuture<Boolean> enforceSecurityLogging(
@Nullable Boolean value, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value));
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean enforceAuditLogging(
+ static CompletableFuture<Boolean> enforceAuditLogging(
@Nullable Boolean value, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value));
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setLockTask(
+ static CompletableFuture<Boolean> setLockTask(
@Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
List<String> packages = Collections.emptyList();
int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
@@ -175,7 +186,7 @@ final class PolicyEnforcerCallbacks {
}
DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
- return true;
+ return AndroidFuture.completedFuture(true);
}
@@ -187,8 +198,8 @@ final class PolicyEnforcerCallbacks {
* rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
* when the policy is set, and not during system boot or other situations.
*/
- static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
- PolicyKey policyKey) {
+ static CompletableFuture<Boolean> setApplicationRestrictions(Bundle bundle, Context context,
+ Integer userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
PackagePolicyKey key = (PackagePolicyKey) policyKey;
String packageName = key.getPackageName();
@@ -198,12 +209,32 @@ final class PolicyEnforcerCallbacks {
changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
});
- return true;
+ return AndroidFuture.completedFuture(true);
+ }
+
+ public static CompletableFuture<Boolean> setAutoTimePolicy(
+ Integer policy, Context context, Integer userId, PolicyKey policyKey) {
+ if (!Flags.setAutoTimeEnabledCoexistence()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimePolicy while flag is off.");
+ return AndroidFuture.completedFuture(true);
+ }
+ return Binder.withCleanCallingIdentity(() -> {
+ Objects.requireNonNull(context);
+ if (policy != null
+ && policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+ return AndroidFuture.completedFuture(false);
+ }
+ int enabled = policy != null && policy == DevicePolicyManager.AUTO_TIME_ENABLED ? 1 : 0;
+ return AndroidFuture.completedFuture(
+ Settings.Global.putInt(
+ context.getContentResolver(), Settings.Global.AUTO_TIME, enabled));
+ });
}
private static class BlockingCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final AtomicReference<Boolean> mValue = new AtomicReference<>();
+
public void trigger(Boolean value) {
mValue.set(value);
mLatch.countDown();
@@ -220,7 +251,7 @@ final class PolicyEnforcerCallbacks {
// TODO: when a local policy exists for a user, this callback will be invoked for this user
// individually as well as for USER_ALL. This can be optimized by separating local and global
// enforcement in the policy engine.
- static boolean setUserControlDisabledPackages(
+ static CompletableFuture<Boolean> setUserControlDisabledPackages(
@Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
PackageManagerInternal pmi =
@@ -246,7 +277,7 @@ final class PolicyEnforcerCallbacks {
}
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
/** Handles USER_ALL expanding it into the list of all intact users. */
@@ -271,7 +302,7 @@ final class PolicyEnforcerCallbacks {
}
}
- static boolean addPersistentPreferredActivity(
+ static CompletableFuture<Boolean> addPersistentPreferredActivity(
@Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -297,13 +328,13 @@ final class PolicyEnforcerCallbacks {
Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setUninstallBlocked(
+ static CompletableFuture<Boolean> setUninstallBlocked(
@Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -314,14 +345,14 @@ final class PolicyEnforcerCallbacks {
packageName,
uninstallBlocked != null && uninstallBlocked,
userId);
- return true;
- }));
+ return AndroidFuture.completedFuture(true);
+ });
}
- static boolean setUserRestriction(
+ static CompletableFuture<Boolean> setUserRestriction(
@Nullable Boolean enabled, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof UserRestrictionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey);
@@ -331,14 +362,14 @@ final class PolicyEnforcerCallbacks {
UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
userManager.setUserRestriction(
userId, parsedKey.getRestriction(), enabled != null && enabled);
- return true;
- }));
+ return AndroidFuture.completedFuture(true);
+ });
}
- static boolean setApplicationHidden(
+ static CompletableFuture<Boolean> setApplicationHidden(
@Nullable Boolean hide, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -346,12 +377,13 @@ final class PolicyEnforcerCallbacks {
PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
String packageName = Objects.requireNonNull(parsedKey.getPackageName());
IPackageManager packageManager = AppGlobals.getPackageManager();
- return packageManager.setApplicationHiddenSettingAsUser(
- packageName, hide != null && hide, userId);
- }));
+ return AndroidFuture.completedFuture(
+ packageManager.setApplicationHiddenSettingAsUser(
+ packageName, hide != null && hide, userId));
+ });
}
- static boolean setScreenCaptureDisabled(
+ static CompletableFuture<Boolean> setScreenCaptureDisabled(
@Nullable Boolean disabled, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -363,10 +395,10 @@ final class PolicyEnforcerCallbacks {
updateScreenCaptureDisabled();
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setContentProtectionPolicy(
+ static CompletableFuture<Boolean> setContentProtectionPolicy(
@Nullable Integer value,
@NonNull Context context,
@UserIdInt Integer userId,
@@ -378,7 +410,7 @@ final class PolicyEnforcerCallbacks {
cacheImpl.setContentProtectionPolicy(userId, value);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static void updateScreenCaptureDisabled() {
@@ -393,7 +425,7 @@ final class PolicyEnforcerCallbacks {
});
}
- static boolean setPersonalAppsSuspended(
+ static CompletableFuture<Boolean> setPersonalAppsSuspended(
@Nullable Boolean suspended, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -404,7 +436,7 @@ final class PolicyEnforcerCallbacks {
.unsuspendAdminSuspendedPackages(userId);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static void suspendPersonalAppsInPackageManager(Context context, int userId) {
@@ -418,13 +450,53 @@ final class PolicyEnforcerCallbacks {
}
}
- static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) {
+ static CompletableFuture<Boolean> setUsbDataSignalingEnabled(@Nullable Boolean value,
+ @NonNull Context context) {
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
boolean enabled = value == null || value;
DevicePolicyManagerService.updateUsbDataSignal(context, enabled);
- return true;
+ return AndroidFuture.completedFuture(true);
});
}
+
+ static CompletableFuture<Boolean> setMtePolicy(
+ @Nullable Integer mtePolicy, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ if (mtePolicy == null) {
+ mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
+ final Set<Integer> allowedModes =
+ Set.of(
+ DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+ DevicePolicyManager.MTE_DISABLED,
+ DevicePolicyManager.MTE_ENABLED);
+ if (!allowedModes.contains(mtePolicy)) {
+ Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy);
+ return AndroidFuture.completedFuture(false);
+ }
+
+ final String mteDpmSystemProperty =
+ "ro.arm64.memtag.bootctl_device_policy_manager";
+ final String mteSettingsSystemProperty =
+ "ro.arm64.memtag.bootctl_settings_toggle";
+ final String mteControlProperty = "arm64.memtag.bootctl";
+
+ final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty,
+ SystemProperties.getBoolean(mteSettingsSystemProperty, false));
+ if (!isAvailable) {
+ return AndroidFuture.completedFuture(false);
+ }
+
+ if (mtePolicy == DevicePolicyManager.MTE_ENABLED) {
+ SystemProperties.set(mteControlProperty, "memtag");
+ } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) {
+ SystemProperties.set(mteControlProperty, "memtag-off");
+ } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ SystemProperties.set(mteControlProperty, "default");
+ }
+
+ return AndroidFuture.completedFuture(true);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index b81348969f7d..0d9dbaaec6b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,7 +19,6 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
-import android.app.admin.flags.Flags;
import android.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -41,7 +40,6 @@ final class PolicyState<V> {
private static final String TAG = "PolicyState";
private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
- private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
@@ -225,10 +223,6 @@ final class PolicyState<V> {
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- mPolicyDefinition.saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
-
if (mCurrentResolvedPolicy != null) {
serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
@@ -298,18 +292,6 @@ final class PolicyState<V> {
+ (value == null ? "null" : value));
}
break;
- case TAG_POLICY_DEFINITION_ENTRY:
- if (Flags.dontReadPolicyDefinition()) {
- // Should be passed by the caller.
- Objects.requireNonNull(policyDefinition);
- } else {
- policyDefinition = PolicyDefinition.readFromXml(parser);
- if (policyDefinition == null) {
- Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
- + "PolicyDefinition is null");
- }
- }
- break;
case TAG_RESOLVED_VALUE_ENTRY:
if (policyDefinition == null) {
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index cc5573bb01d8..f34ec72d7e27 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -19,6 +19,7 @@ package com.android.server.policy;
import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
@@ -71,6 +72,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
private static final int DEVICE_STATE_OPENED = 2;
private static final int DEVICE_STATE_REAR_DISPLAY = 3;
private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
+ private static final int DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT = 5;
private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
@@ -130,14 +132,17 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
&& hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
}),
- createConfig(getOpenedDeviceState(), /* activeStatePredicate= */
- ALLOWED),
- createConfig(getRearDisplayDeviceState(), /* activeStatePredicate= */
- NOT_ALLOWED),
- createConfig(getDualDisplayDeviceState(), /* activeStatePredicate= */
- NOT_ALLOWED, /* availabilityPredicate= */
- provider -> !mIsDualDisplayBlockingEnabled
- || provider.hasNoConnectedExternalDisplay())};
+ createConfig(getOpenedDeviceState(),
+ /* activeStatePredicate= */ ALLOWED),
+ createConfig(getRearDisplayDeviceState(),
+ /* activeStatePredicate= */ NOT_ALLOWED),
+ createConfig(getDualDisplayDeviceState(),
+ /* activeStatePredicate= */ NOT_ALLOWED,
+ /* availabilityPredicate= */ provider -> !mIsDualDisplayBlockingEnabled
+ || provider.hasNoConnectedExternalDisplay()),
+ createConfig(getRearDisplayOuterDefaultState(),
+ /* activeStatePredicate= */ NOT_ALLOWED)
+ };
}
private DeviceStatePredicateWrapper createClosedConfiguration(
@@ -266,4 +271,24 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
.setSystemProperties(systemProperties)
.build());
}
+
+ /**
+ * Returns the {link DeviceState.Configuration} that represents the new rear display state
+ * where the inner display is also enabled, showing a system affordance to exit the state.
+ */
+ @NonNull
+ private DeviceState getRearDisplayOuterDefaultState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_EMULATED_ONLY,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+ PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(
+ DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT,
+ "REAR_DISPLAY_OUTER_DEFAULT")
+ .setSystemProperties(systemProperties)
+ .build());
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e4ab7685d787..1472da687199 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.media.tv.flags.Flags.mediaQualityFw;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -27,7 +28,9 @@ import static android.system.OsConstants.O_CLOEXEC;
import static android.system.OsConstants.O_RDONLY;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.server.utils.TimingsTraceAndSlog.SYSTEM_SERVER_TIMING_TAG;
+import static com.android.tradeinmode.flags.Flags.enableTradeInMode;
import android.annotation.NonNull;
import android.annotation.StringRes;
@@ -103,13 +106,14 @@ import com.android.i18n.timezone.ZoneInfoDb;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.ApplicationSharedMemory;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogConfigurationServiceImpl;
-import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
@@ -117,7 +121,6 @@ import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accounts.AccountManagerService;
-import com.android.server.adaptiveauth.AdaptiveAuthService;
import com.android.server.adb.AdbService;
import com.android.server.alarm.AlarmManagerService;
import com.android.server.am.ActivityManagerService;
@@ -159,7 +162,7 @@ import com.android.server.contentsuggestions.ContentSuggestionsManagerService;
import com.android.server.contextualsearch.ContextualSearchManagerService;
import com.android.server.coverage.CoverageService;
import com.android.server.cpu.CpuMonitorService;
-import com.android.server.crashrecovery.CrashRecoveryModule;
+import com.android.server.crashrecovery.CrashRecoveryAdaptor;
import com.android.server.credentials.CredentialManagerService;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -189,6 +192,7 @@ import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
import com.android.server.media.metrics.MediaMetricsManagerService;
import com.android.server.media.projection.MediaProjectionManagerService;
+import com.android.server.media.quality.MediaQualityService;
import com.android.server.midi.MidiService;
import com.android.server.musicrecognition.MusicRecognitionManagerService;
import com.android.server.net.NetworkManagementService;
@@ -197,14 +201,15 @@ import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
-import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.os.instrumentation.DynamicInstrumentationManagerService;
import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
+import com.android.server.pinner.PinnerService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.BackgroundInstallControlService;
@@ -245,6 +250,10 @@ import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.advancedprotection.AdvancedProtectionService;
+import com.android.server.security.authenticationpolicy.AuthenticationPolicyService;
+import com.android.server.security.authenticationpolicy.SecureLockDeviceService;
+import com.android.server.security.intrusiondetection.IntrusionDetectionService;
import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.selinux.SelinuxAuditLogsService;
import com.android.server.sensorprivacy.SensorPrivacyService;
@@ -327,8 +336,6 @@ public final class SystemServer implements Dumpable {
* Implementation class names for services in the {@code SYSTEMSERVERCLASSPATH}
* from {@code PRODUCT_SYSTEM_SERVER_JARS} that are *not* in {@code services.jar}.
*/
- private static final String ARC_NETWORK_SERVICE_CLASS =
- "com.android.server.arc.net.ArcNetworkService";
private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
"com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
private static final String ARC_SYSTEM_HEALTH_SERVICE =
@@ -385,6 +392,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
"com.android.server.adservices.AdServicesManagerService$Lifecycle";
+ private static final String ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS =
+ "com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService";
private static final String ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS =
"com.android.server.ondevicepersonalization."
+ "OnDevicePersonalizationSystemService$Lifecycle";
@@ -416,6 +425,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.wifi.aware.WifiAwareService";
private static final String WIFI_P2P_SERVICE_CLASS =
"com.android.server.wifi.p2p.WifiP2pService";
+ private static final String WIFI_USD_SERVICE_CLASS =
+ "com.android.server.wifi.usd.UsdService";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
@@ -952,6 +963,12 @@ public final class SystemServer implements Dumpable {
// Setup the default WTF handler
RuntimeInit.setDefaultApplicationWtfHandler(SystemServer::handleEarlySystemWtf);
+ // Initialize the application shared memory region.
+ // This needs to happen before any system services are started,
+ // as they may rely on the shared memory region having been initialized.
+ ApplicationSharedMemory instance = ApplicationSharedMemory.create();
+ ApplicationSharedMemory.setInstance(instance);
+
// Start services.
try {
t.traceBegin("StartServices");
@@ -1126,7 +1143,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("InitializeProtoLog");
- ProtoLog.init(ProtoLogGroup.values());
+ ProtoLog.init(WmProtoLogGroups.values());
t.traceEnd();
// Platform compat service is used by ActivityManagerService, PackageManagerService, and
@@ -1242,12 +1259,12 @@ public final class SystemServer implements Dumpable {
if (!Flags.refactorCrashrecovery()) {
// Initialize RescueParty.
- RescueParty.registerHealthObserver(mSystemContext);
+ CrashRecoveryAdaptor.rescuePartyRegisterHealthObserver(mSystemContext);
if (!Flags.recoverabilityDetection()) {
// Now that we have the bare essentials of the OS up and running, take
// note that we just booted, which might send out a rescue party if
// we're stuck in a runtime restart loop.
- PackageWatchdog.getInstance(mSystemContext).noteBoot();
+ CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext);
}
}
@@ -1515,6 +1532,8 @@ public final class SystemServer implements Dumpable {
boolean isTv = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
+ boolean isAutomotive = RoSystemFeatures.hasFeatureAutomotive(context);
+
boolean enableVrService = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
@@ -1627,7 +1646,8 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
t.traceEnd();
- if (android.app.supervision.flags.Flags.supervisionApi()) {
+ if (android.app.supervision.flags.Flags.supervisionApi()
+ && (!isWatch || android.app.supervision.flags.Flags.supervisionApiOnWear())) {
t.traceBegin("StartSupervisionService");
mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
t.traceEnd();
@@ -1657,7 +1677,12 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("StartInputManagerService");
- inputManager = new InputManagerService(context);
+ if (inputManagerLifecycleSupport()) {
+ inputManager = mSystemServiceManager.startService(
+ InputManagerService.Lifecycle.class).getService();
+ } else {
+ inputManager = new InputManagerService(context);
+ }
t.traceEnd();
t.traceBegin("DeviceStateManagerService");
@@ -1678,8 +1703,10 @@ public final class SystemServer implements Dumpable {
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
| DUMP_FLAG_PROTO);
- ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
- /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
+ if (!inputManagerLifecycleSupport()) {
+ ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
+ /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
+ }
t.traceEnd();
t.traceBegin("SetWindowManagerService");
@@ -1765,11 +1792,32 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(LogcatManagerService.class);
t.traceEnd();
+ if (!isWatch && !isTv && !isAutomotive
+ && android.security.Flags.aflApi()) {
+ t.traceBegin("StartIntrusionDetectionService");
+ mSystemServiceManager.startService(IntrusionDetectionService.class);
+ t.traceEnd();
+ }
+
if (AppFunctionManagerConfiguration.isSupported(context)) {
t.traceBegin("StartAppFunctionManager");
mSystemServiceManager.startService(AppFunctionManagerService.class);
t.traceEnd();
}
+
+ if (!isWatch && !isTv && !isAutomotive
+ && android.security.Flags.aapmApi()) {
+ t.traceBegin("StartAdvancedProtectionService");
+ mSystemServiceManager.startService(AdvancedProtectionService.Lifecycle.class);
+ t.traceEnd();
+ }
+
+ if (!isWatch && !isTv && !isAutomotive && enableTradeInMode()) {
+ t.traceBegin("StartTradeInModeService");
+ mSystemServiceManager.startService(TradeInModeService.class);
+ t.traceEnd();
+ }
+
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
@@ -2115,27 +2163,23 @@ public final class SystemServer implements Dumpable {
if (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI)) {
// Wifi Service must be started first for wifi-related services.
- if (!isArc) {
- t.traceBegin("StartWifi");
- mSystemServiceManager.startServiceFromJar(
- WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
- t.traceEnd();
- t.traceBegin("StartWifiScanning");
+ t.traceBegin("StartWifi");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ t.traceBegin("StartWifiScanning");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ // Start USD service
+ if (android.net.wifi.flags.Flags.usd()) {
+ t.traceBegin("StartUsd");
mSystemServiceManager.startServiceFromJar(
- WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ WIFI_USD_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
t.traceEnd();
}
}
- // ARC - ArcNetworkService registers the ARC network stack and replaces the
- // stock WiFi service in both ARC++ container and ARCVM. Always starts the ARC network
- // stack regardless of whether FEATURE_WIFI is enabled/disabled (b/254755875).
- if (isArc) {
- t.traceBegin("StartArcNetworking");
- mSystemServiceManager.startService(ARC_NETWORK_SERVICE_CLASS);
- t.traceEnd();
- }
-
if (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_RTT)) {
t.traceBegin("StartRttService");
@@ -2601,6 +2645,12 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
+ if (mediaQualityFw() && isTv) {
+ t.traceBegin("StartMediaQuality");
+ mSystemServiceManager.startService(MediaQualityService.class);
+ t.traceEnd();
+ }
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
t.traceBegin("StartMediaResourceMonitor");
mSystemServiceManager.startService(MediaResourceMonitorService.class);
@@ -2658,9 +2708,15 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(AuthService.class);
t.traceEnd();
+ if (android.security.Flags.secureLockdown()) {
+ t.traceBegin("StartSecureLockDeviceService.Lifecycle");
+ mSystemServiceManager.startService(SecureLockDeviceService.Lifecycle.class);
+ t.traceEnd();
+ }
+
if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
- t.traceBegin("StartAdaptiveAuthService");
- mSystemServiceManager.startService(AdaptiveAuthService.class);
+ t.traceBegin("StartAuthenticationPolicyService");
+ mSystemServiceManager.startService(AuthenticationPolicyService.class);
t.traceEnd();
}
@@ -2768,8 +2824,9 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(WEAR_MODE_SERVICE_CLASS);
t.traceEnd();
- boolean enableWristOrientationService = SystemProperties.getBoolean(
- "config.enable_wristorientation", false);
+ boolean enableWristOrientationService =
+ !android.server.Flags.migrateWristOrientation()
+ && SystemProperties.getBoolean("config.enable_wristorientation", false);
if (enableWristOrientationService) {
t.traceBegin("StartWristOrientationService");
mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS);
@@ -2899,6 +2956,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(TracingServiceProxy.class);
t.traceEnd();
+ // UprobeStats DynamicInstrumentationManager
+ if (android.uprobestats.flags.Flags.executableMethodFileOffsets()) {
+ t.traceBegin("StartDynamicInstrumentationManager");
+ mSystemServiceManager.startService(DynamicInstrumentationManagerService.class);
+ t.traceEnd();
+ }
+
// It is now time to start up the app processes...
t.traceBegin("MakeLockSettingsServiceReady");
@@ -2998,7 +3062,7 @@ public final class SystemServer implements Dumpable {
if (Flags.refactorCrashrecovery()) {
t.traceBegin("StartCrashRecoveryModule");
- mSystemServiceManager.startService(CrashRecoveryModule.Lifecycle.class);
+ CrashRecoveryAdaptor.initializeCrashrecoveryModuleService(mSystemServiceManager);
t.traceEnd();
} else {
if (Flags.recoverabilityDetection()) {
@@ -3006,7 +3070,7 @@ public final class SystemServer implements Dumpable {
// with package watchdog.
// Note that we just booted, which might send out a rescue party if we're stuck in a
// runtime restart loop.
- PackageWatchdog.getInstance(mSystemContext).noteBoot();
+ CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext);
}
}
@@ -3036,9 +3100,13 @@ public final class SystemServer implements Dumpable {
}
t.traceEnd();
- t.traceBegin("GameManagerService");
- mSystemServiceManager.startService(GameManagerService.Lifecycle.class);
- t.traceEnd();
+ if (!isWatch || !android.server.Flags.removeGameManagerServiceFromWear()) {
+ t.traceBegin("GameManagerService");
+ mSystemServiceManager.startService(GameManagerService.Lifecycle.class);
+ t.traceEnd();
+ } else {
+ Slog.d(TAG, "Not starting GameManagerService");
+ }
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
t.traceBegin("UwbService");
@@ -3160,7 +3228,7 @@ public final class SystemServer implements Dumpable {
}, WEBVIEW_PREPARATION);
}
- if (RoSystemFeatures.hasFeatureAutomotive(context)) {
+ if (isAutomotive) {
t.traceBegin("StartCarServiceHelperService");
final SystemService cshs = mSystemServiceManager
.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
@@ -3315,16 +3383,18 @@ public final class SystemServer implements Dumpable {
reportWtf("Notifying NetworkTimeService running", e);
}
t.traceEnd();
- t.traceBegin("MakeInputManagerServiceReady");
- try {
- // TODO(BT) Pass parameter to input manager
- if (inputManagerF != null) {
- inputManagerF.systemRunning();
+ if (!inputManagerLifecycleSupport()) {
+ t.traceBegin("MakeInputManagerServiceReady");
+ try {
+ // TODO(BT) Pass parameter to input manager
+ if (inputManagerF != null) {
+ inputManagerF.systemRunning();
+ }
+ } catch (Throwable e) {
+ reportWtf("Notifying InputManagerService running", e);
}
- } catch (Throwable e) {
- reportWtf("Notifying InputManagerService running", e);
+ t.traceEnd();
}
- t.traceEnd();
t.traceBegin("MakeTelephonyRegistryReady");
try {
if (telephonyRegistryF != null) {
@@ -3403,7 +3473,7 @@ public final class SystemServer implements Dumpable {
private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
t.traceBegin("startOnDeviceIntelligenceManagerService");
- mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+ mSystemServiceManager.startService(ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS);
t.traceEnd();
}
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 7ab209aa5efb..141966dd6306 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -38,8 +38,24 @@ flag {
}
flag {
+ name: "migrate_wrist_orientation"
+ namespace: "wear_frameworks"
+ description: "Migrate wrist orientation service functionality to wear settings service"
+ bug: "352725980"
+ is_fixed_read_only: true
+}
+
+flag {
name: "allow_network_time_update_service"
namespace: "wear_systems"
description: "Allow NetworkTimeUpdateService on Wear"
bug: "327508176"
+}
+
+flag {
+ name: "remove_game_manager_service_from_wear"
+ namespace: "wear_frameworks"
+ description: "Remove GameManagerService from Wear"
+ bug: "340929737"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 114fe324f016..945720544991 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,9 @@
<version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
+ <hal format="aidl">
+ <name>android.frameworks.devicestate</name>
+ <version>1</version>
+ <fqname>IDeviceStateService/default</fqname>
+ </hal>
</manifest>
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index cc340c0a5f79..891c3349a43f 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -58,6 +58,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.EventLog;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -1737,6 +1738,11 @@ public class MidiService extends IMidiManager.Stub {
pw.decreaseIndent();
}
+ @Override
+ protected void onUnhandledException(int code, int flags, Exception e) {
+ Slog.wtf(TAG, "Uncaught exception in AudioService: " + code + ", " + flags, e);
+ }
+
@GuardedBy("mUsbMidiLock")
private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) {
String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
diff --git a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
index ff901af3defa..30df4c821134 100644
--- a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
@@ -96,6 +96,8 @@ class CallLogQueryHelper {
} catch (SecurityException ex) {
Slog.e(TAG, "Query call log failed: " + ex);
return false;
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying call log.", e);
}
return hasResults;
}
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 2505abf2d160..2bd9d87b0124 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -151,9 +151,11 @@ class ContactsQueryHelper {
found = true;
}
} catch (SQLiteException exception) {
- Slog.w("SQLite exception when querying contacts.", exception);
+ Slog.w(TAG, "SQLite exception when querying contacts.", exception);
} catch (IllegalArgumentException exception) {
- Slog.w("Illegal Argument exception when querying contacts.", exception);
+ Slog.w(TAG, "Illegal Argument exception when querying contacts.", exception);
+ } catch (Exception exception) {
+ Slog.e(TAG, "Exception when querying contacts.", exception);
}
if (found && lookupKey != null && hasPhoneNumber) {
return queryPhoneNumber(lookupKey);
@@ -181,6 +183,8 @@ class ContactsQueryHelper {
mPhoneNumber = cursor.getString(phoneNumIdx);
}
}
+ } catch (Exception exception) {
+ Slog.e(TAG, "Exception when querying contact phone number.", exception);
}
return true;
}
diff --git a/services/people/java/com/android/server/people/data/MmsQueryHelper.java b/services/people/java/com/android/server/people/data/MmsQueryHelper.java
index 39dba9c73ba2..414a523fb186 100644
--- a/services/people/java/com/android/server/people/data/MmsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/MmsQueryHelper.java
@@ -100,6 +100,8 @@ class MmsQueryHelper {
}
}
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying MMS table.", e);
} finally {
Binder.defaultBlockingForCurrentThread();
}
@@ -133,6 +135,8 @@ class MmsQueryHelper {
address = cursor.getString(addrIndex);
}
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying MMS address table.", e);
}
if (!Mms.isPhoneNumber(address)) {
return null;
diff --git a/services/people/java/com/android/server/people/data/SmsQueryHelper.java b/services/people/java/com/android/server/people/data/SmsQueryHelper.java
index a5eb3a581616..f8ff3abc8e4c 100644
--- a/services/people/java/com/android/server/people/data/SmsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/SmsQueryHelper.java
@@ -98,6 +98,8 @@ class SmsQueryHelper {
}
}
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying SMS table.", e);
} finally {
Binder.defaultBlockingForCurrentThread();
}
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index f5360eb9a56a..6b28047c2610 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -213,7 +213,8 @@ class ShareTargetPredictor extends AppTargetPredictor {
}
private int getShareEventType(IntentFilter intentFilter) {
- String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
+ String mimeType = (intentFilter != null && intentFilter.countDataTypes() > 0)
+ ? intentFilter.getDataType(0) : null;
return getDataManager().mimeTypeToShareEventType(mimeType);
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index 15c9b9f7a13d..a4546aebef21 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -297,6 +297,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
private const val MASK_ANY_FIXED =
PermissionFlags.USER_SET or
+ PermissionFlags.ONE_TIME or
PermissionFlags.USER_FIXED or
PermissionFlags.POLICY_FIXED or
PermissionFlags.SYSTEM_FIXED
diff --git a/services/proguard.flags b/services/proguard.flags
index 1ab10dcd847b..8d8b418ced0b 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -47,6 +47,9 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; }
+# allow invoking start-service using class name in both apex and services jar.
+-keep,allowoptimization,allowaccessmodification class com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService { *; }
+
# Keep all aconfig Flag class as they might be statically referenced by other packages
# An merge or inlining could lead to missing dependencies that cause run time errors
-keepclassmembernames class android.**.Flags, com.android.**.Flags { public *; }
@@ -85,7 +88,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaMidiDevice { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; }
--keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$VibratorManagerNativeCallbacks { *; }
-keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** {
*** *FromNative(...);
}
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index a547d0f94ea3..4e9fff230bac 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import static org.testng.Assert.expectThrows;
@@ -96,6 +97,7 @@ public class BackupManagerServiceRoboTest {
@UserIdInt private int mUserTwoId;
@Mock private UserBackupManagerService mUserSystemService;
@Mock private UserBackupManagerService mUserOneService;
+ @Mock private BackupAgentConnectionManager mUserOneBackupAgentConnectionManager;
@Mock private UserBackupManagerService mUserTwoService;
/** Setup */
@@ -116,6 +118,9 @@ public class BackupManagerServiceRoboTest {
mShadowContext.grantPermissions(BACKUP);
mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL);
+ when(mUserOneService.getBackupAgentConnectionManager()).thenReturn(
+ mUserOneBackupAgentConnectionManager);
+
ShadowBinder.setCallingUid(Process.SYSTEM_UID);
}
@@ -226,7 +231,7 @@ public class BackupManagerServiceRoboTest {
backupManagerService.agentConnected(mUserOneId, TEST_PACKAGE, agentBinder);
- verify(mUserOneService).agentConnected(TEST_PACKAGE, agentBinder);
+ verify(mUserOneBackupAgentConnectionManager).agentConnected(TEST_PACKAGE, agentBinder);
}
/** Test that the backup service does not route methods for non-registered users. */
@@ -239,7 +244,8 @@ public class BackupManagerServiceRoboTest {
backupManagerService.agentConnected(mUserTwoId, TEST_PACKAGE, agentBinder);
- verify(mUserOneService, never()).agentConnected(TEST_PACKAGE, agentBinder);
+ verify(mUserOneBackupAgentConnectionManager, never()).agentConnected(TEST_PACKAGE,
+ agentBinder);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 02e0bbfd3519..eb61a40e0ba5 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -30,13 +30,10 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;
import android.app.backup.BackupManager;
@@ -90,7 +87,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -110,7 +106,6 @@ public class UserBackupManagerServiceTest {
private static final String TAG = "BMSTest";
private static final String PACKAGE_1 = "some.package.1";
private static final String PACKAGE_2 = "some.package.2";
- private static final String USER_FACING_PACKAGE = "user.facing.package";
private static final int USER_ID = 10;
@Mock private TransportManager mTransportManager;
@@ -1213,47 +1208,6 @@ public class UserBackupManagerServiceTest {
eq(packageTrackingReceiver), eq(UserHandle.of(USER_ID)), any(), any(), any());
}
- @Test
- public void testFilterUserFacingPackages_shouldSkipUserFacing_filtersUserFacing() {
- List<PackageInfo> packages = Arrays.asList(getPackageInfo(USER_FACING_PACKAGE),
- getPackageInfo(PACKAGE_1));
- UserBackupManagerService backupManagerService = spy(
- createUserBackupManagerServiceAndRunTasks());
- when(backupManagerService.shouldSkipUserFacingData()).thenReturn(true);
- when(backupManagerService.shouldSkipPackage(eq(USER_FACING_PACKAGE))).thenReturn(true);
-
- List<PackageInfo> filteredPackages = backupManagerService.filterUserFacingPackages(
- packages);
-
- assertFalse(containsPackage(filteredPackages, USER_FACING_PACKAGE));
- assertTrue(containsPackage(filteredPackages, PACKAGE_1));
- }
-
- @Test
- public void testFilterUserFacingPackages_shouldNotSkipUserFacing_doesNotFilterUserFacing() {
- List<PackageInfo> packages = Arrays.asList(getPackageInfo(USER_FACING_PACKAGE),
- getPackageInfo(PACKAGE_1));
- UserBackupManagerService backupManagerService = spy(
- createUserBackupManagerServiceAndRunTasks());
- when(backupManagerService.shouldSkipUserFacingData()).thenReturn(false);
- when(backupManagerService.shouldSkipPackage(eq(USER_FACING_PACKAGE))).thenReturn(true);
-
- List<PackageInfo> filteredPackages = backupManagerService.filterUserFacingPackages(
- packages);
-
- assertTrue(containsPackage(filteredPackages, USER_FACING_PACKAGE));
- assertTrue(containsPackage(filteredPackages, PACKAGE_1));
- }
-
- private static boolean containsPackage(List<PackageInfo> packages, String targetPackage) {
- for (PackageInfo packageInfo : packages) {
- if (targetPackage.equals(packageInfo.packageName)) {
- return true;
- }
- }
- return false;
- }
-
private UserBackupManagerService createUserBackupManagerServiceAndRunTasks() {
return BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
USER_ID, mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager);
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 7349c14ef62b..de16b7ee8126 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -107,6 +107,7 @@ import com.android.internal.backup.IBackupTransport;
import com.android.internal.infra.AndroidFuture;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.backup.BackupAgentConnectionManager;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.KeyValueBackupJob;
@@ -167,7 +168,6 @@ import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
-// TODO: Test agents timing out
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
@@ -195,6 +195,7 @@ public class KeyValueBackupTaskTest {
@Mock private IBackupManagerMonitor mMonitor;
@Mock private OnTaskFinishedListener mListener;
@Mock private PackageManagerInternal mPackageManagerInternal;
+ @Mock private BackupAgentConnectionManager mBackupAgentConnectionManager;
private UserBackupManagerService mBackupManagerService;
private TransportData mTransport;
@@ -257,6 +258,8 @@ public class KeyValueBackupTaskTest {
when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir);
when(mBackupManagerService.getDataDir()).thenReturn(mDataDir);
when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);
+ when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn(
+ mBackupAgentConnectionManager);
mBackupHandler = mBackupManagerService.getBackupHandler();
mShadowBackupLooper = shadowOf(mBackupHandler.getLooper());
@@ -749,7 +752,8 @@ public class KeyValueBackupTaskTest {
/**
* Agent unavailable means {@link
- * UserBackupManagerService#bindToAgentSynchronous(ApplicationInfo, int)} returns {@code null}.
+ * BackupAgentConnectionManager#bindToAgentSynchronous(ApplicationInfo, int, int)} returns
+ * {@code null}.
*
* @see #setUpAgent(PackageData)
*/
@@ -805,7 +809,7 @@ public class KeyValueBackupTaskTest {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
doThrow(SecurityException.class)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt());
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
@@ -823,7 +827,7 @@ public class KeyValueBackupTaskTest {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
doThrow(SecurityException.class)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt());
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
@@ -861,7 +865,8 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(mBackupManagerService).setWorkSource(null);
- verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)),
+ eq(false));
}
@Test
@@ -1097,7 +1102,8 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(agentMock.agentBinder).fail(any());
- verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)),
+ eq(false));
}
@Test
@@ -1418,7 +1424,8 @@ public class KeyValueBackupTaskTest {
.isEqualTo("newState".getBytes());
assertCleansUpFiles(mTransport, PM_PACKAGE);
// We don't unbind PM
- verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
+ verify(mBackupAgentConnectionManager, never()).unbindAgent(
+ argThat(applicationInfo(PM_PACKAGE)), eq(false));
}
@Test
@@ -1439,7 +1446,8 @@ public class KeyValueBackupTaskTest {
runTask(task);
- verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
+ verify(mBackupAgentConnectionManager, never()).unbindAgent(
+ argThat(applicationInfo(PM_PACKAGE)), eq(false));
}
@Test
@@ -1642,9 +1650,10 @@ public class KeyValueBackupTaskTest {
runTask(task);
- InOrder inOrder = inOrder(agentMock.agent, mBackupManagerService);
+ InOrder inOrder = inOrder(agentMock.agent, mBackupAgentConnectionManager);
inOrder.verify(agentMock.agent).onQuotaExceeded(anyLong(), eq(1234L));
- inOrder.verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ inOrder.verify(mBackupAgentConnectionManager).unbindAgent(
+ argThat(applicationInfo(PACKAGE_1)), eq(false));
}
@Test
@@ -2634,12 +2643,12 @@ public class KeyValueBackupTaskTest {
doNothing().when(backupAgentBinder).fail(any());
if (packageData.available) {
doReturn(backupAgentBinder)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(),
anyInt());
} else {
doReturn(null)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(),
anyInt());
}
@@ -2976,7 +2985,8 @@ public class KeyValueBackupTaskTest {
private void assertCleansUpFilesAndAgent(TransportData transport, PackageData packageData) {
assertCleansUpFiles(transport, packageData);
- verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(packageData)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData)),
+ eq(false));
}
private void assertCleansUpFiles(TransportData transport, PackageData packageData) {
diff --git a/services/supervision/Android.bp b/services/supervision/Android.bp
index 93a0c4af7891..aefbbcadcc1d 100644
--- a/services/supervision/Android.bp
+++ b/services/supervision/Android.bp
@@ -19,4 +19,7 @@ java_library_static {
defaults: ["platform_service_defaults"],
srcs: [":services.supervision-sources"],
libs: ["services.core"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 7ffd0eca9b96..0ccaa6043f5f 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -18,33 +18,69 @@ package com.android.server.supervision;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.supervision.ISupervisionManager;
+import android.app.supervision.SupervisionManagerInternal;
+import android.app.supervision.flags.Flags;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.util.SparseArray;
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
+import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
-/**
- * Service for handling system supervision.
- */
+/** Service for handling system supervision. */
public class SupervisionService extends ISupervisionManager.Stub {
private static final String LOG_TAG = "SupervisionService";
private final Context mContext;
+ // TODO(b/362756788): Does this need to be a LockGuard lock?
+ private final Object mLockDoNoUseDirectly = new Object();
+
+ @GuardedBy("getLockObject()")
+ private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
+
+ private final DevicePolicyManagerInternal mDpmInternal;
+ private final PackageManager mPackageManager;
+ private final UserManagerInternal mUserManagerInternal;
+
public SupervisionService(Context context) {
- mContext = context.createAttributionContext("SupervisionService");
+ mContext = context.createAttributionContext(LOG_TAG);
+ mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+ mPackageManager = context.getPackageManager();
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
}
@Override
- public boolean isSupervisionEnabled() {
- return false;
+ public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ synchronized (getLockObject()) {
+ return getUserDataLocked(userId).supervisionEnabled;
+ }
}
@Override
@@ -54,17 +90,75 @@ public class SupervisionService extends ISupervisionManager.Stub {
@Nullable FileDescriptor err,
@NonNull String[] args,
@Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ @NonNull ResultReceiver resultReceiver)
+ throws RemoteException {
new SupervisionServiceShellCommand(this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override
protected void dump(
- @NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
+ @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return;
+
+ try (var pw = new IndentingPrintWriter(printWriter, " ")) {
+ pw.println("SupervisionService state:");
+ pw.increaseIndent();
+
+ List<UserInfo> users = mUserManagerInternal.getUsers(false);
+ synchronized (getLockObject()) {
+ for (var user : users) {
+ getUserDataLocked(user.id).dump(pw);
+ pw.println();
+ }
+ }
+ }
+ }
- fout.println("Supervision enabled: " + isSupervisionEnabled());
+ private Object getLockObject() {
+ return mLockDoNoUseDirectly;
+ }
+
+ @NonNull
+ @GuardedBy("getLockObject()")
+ SupervisionUserData getUserDataLocked(@UserIdInt int userId) {
+ SupervisionUserData data = mUserData.get(userId);
+ if (data == null) {
+ // TODO(b/362790738): Do not create user data for nonexistent users.
+ data = new SupervisionUserData(userId);
+ mUserData.append(userId, data);
+ }
+ return data;
+ }
+
+ void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ synchronized (getLockObject()) {
+ getUserDataLocked(userId).supervisionEnabled = enabled;
+ }
+ }
+
+ /** Ensures that supervision is enabled when supervision app is the profile owner. */
+ private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
+ if (isProfileOwner(userId)) {
+ setSupervisionEnabledForUser(userId, true);
+ } else {
+ // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
+ // This might only be possible after introducing specific and public APIs to enable
+ // supervision.
+ setSupervisionEnabledForUser(userId, false);
+ }
+ }
+
+ /** Returns whether the supervision app has profile owner status. */
+ private boolean isProfileOwner(@UserIdInt int userId) {
+ ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
+ return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName());
+ }
+
+ /** Returns whether the given package name belongs to the supervision role holder. */
+ private boolean isSupervisionAppPackage(String packageName) {
+ return packageName.equals(
+ mContext.getResources().getString(R.string.config_systemSupervision));
}
public static class Lifecycle extends SystemService {
@@ -75,9 +169,101 @@ public class SupervisionService extends ISupervisionManager.Stub {
mSupervisionService = new SupervisionService(context);
}
+ @VisibleForTesting
+ Lifecycle(Context context, SupervisionService supervisionService) {
+ super(context);
+ mSupervisionService = supervisionService;
+ }
+
@Override
public void onStart() {
+ publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
+ if (Flags.enableSyncWithDpm()) {
+ registerProfileOwnerListener();
+ }
+ }
+
+ @VisibleForTesting
+ void registerProfileOwnerListener() {
+ IntentFilter poIntentFilter = new IntentFilter();
+ poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
+ poIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ getContext()
+ .registerReceiverForAllUsers(
+ new ProfileOwnerBroadcastReceiver(),
+ poIntentFilter,
+ /* brodcastPermission= */ null,
+ /* scheduler= */ null);
+ }
+
+ @Override
+ public void onUserStarting(@NonNull TargetUser user) {
+ if (Flags.enableSyncWithDpm() && !user.isPreCreated()) {
+ mSupervisionService.syncStateWithDevicePolicyManager(user.getUserIdentifier());
+ }
+ }
+
+ private final class ProfileOwnerBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mSupervisionService.syncStateWithDevicePolicyManager(getSendingUserId());
+ }
+ }
+ }
+
+ final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl();
+
+ private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
+ @Override
+ public boolean isActiveSupervisionApp(int uid) {
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null) {
+ return false;
+ }
+ for (var packageName : packages) {
+ if (SupervisionService.this.isSupervisionAppPackage(packageName)) {
+ int userId = UserHandle.getUserId(uid);
+ return SupervisionService.this.isSupervisionEnabledForUser(userId);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ return SupervisionService.this.isSupervisionEnabledForUser(userId);
+ }
+
+ @Override
+ public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ SupervisionService.this.setSupervisionEnabledForUser(userId, enabled);
+ }
+
+ @Override
+ public boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId) {
+ synchronized (getLockObject()) {
+ return getUserDataLocked(userId).supervisionLockScreenEnabled;
+ }
+ }
+
+ @Override
+ public void setSupervisionLockscreenEnabledForUser(
+ @UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options) {
+ synchronized (getLockObject()) {
+ SupervisionUserData data = getUserDataLocked(userId);
+ data.supervisionLockScreenEnabled = enabled;
+ data.supervisionLockScreenOptions = options;
+ }
+ }
+ }
+
+ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ synchronized (getLockObject()) {
+ mUserData.remove(user.id);
+ }
}
}
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
index 3aba24a3d4a5..2adaae3943f1 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
@@ -17,8 +17,7 @@
package com.android.server.supervision;
import android.os.ShellCommand;
-
-import java.io.PrintWriter;
+import android.os.UserHandle;
public class SupervisionServiceShellCommand extends ShellCommand {
private final SupervisionService mService;
@@ -32,30 +31,29 @@ public class SupervisionServiceShellCommand extends ShellCommand {
if (cmd == null) {
return handleDefaultCommands(null);
}
- final PrintWriter pw = getOutPrintWriter();
switch (cmd) {
- case "help": return help(pw);
- case "is-enabled": return isEnabled(pw);
+ case "enable": return setEnabled(true);
+ case "disable": return setEnabled(false);
default: return handleDefaultCommands(cmd);
}
}
- private int help(PrintWriter pw) {
- pw.println("Supervision service commands:");
- pw.println(" help");
- pw.println(" Prints this help text");
- pw.println(" is-enabled");
- pw.println(" Is supervision enabled");
- return 0;
- }
-
- private int isEnabled(PrintWriter pw) {
- pw.println(mService.isSupervisionEnabled());
+ private int setEnabled(boolean enabled) {
+ final var pw = getOutPrintWriter();
+ final var userId = UserHandle.parseUserArg(getNextArgRequired());
+ mService.setSupervisionEnabledForUser(userId, enabled);
return 0;
}
@Override
public void onHelp() {
- help(getOutPrintWriter());
+ final var pw = getOutPrintWriter();
+ pw.println("Supervision service (supervision) commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text");
+ pw.println(" enable <USER_ID>");
+ pw.println(" Enables supervision for the given user.");
+ pw.println(" disable <USER_ID>");
+ pw.println(" Disables supervision for the given user.");
}
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
new file mode 100644
index 000000000000..1dd48f581bf4
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.supervision;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.PersistableBundle;
+import android.util.IndentingPrintWriter;
+
+/** User specific data, used internally by the {@link SupervisionService}. */
+public class SupervisionUserData {
+ public final @UserIdInt int userId;
+ public boolean supervisionEnabled;
+ public boolean supervisionLockScreenEnabled;
+ @Nullable public PersistableBundle supervisionLockScreenOptions;
+
+ public SupervisionUserData(@UserIdInt int userId) {
+ this.userId = userId;
+ }
+
+ void dump(@NonNull IndentingPrintWriter pw) {
+ pw.println();
+ pw.println("User " + userId + ":");
+ pw.increaseIndent();
+ pw.println("supervisionEnabled: " + supervisionEnabled);
+ pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled);
+ pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions);
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/supervision/lint-baseline.xml b/services/supervision/lint-baseline.xml
new file mode 100644
index 000000000000..f2a501037447
--- /dev/null
+++ b/services/supervision/lint-baseline.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="isSupervisionEnabled should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/supervision/java/com/android/server/supervision/SupervisionService.java"
+ line="45"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="onShellCommand should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/supervision/java/com/android/server/supervision/SupervisionService.java"
+ line="50"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="dump should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/supervision/java/com/android/server/supervision/SupervisionService.java"
+ line="62"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="SimpleRequiresNoPermission"
+ message="Method isSupervisionEnabled doesn&apos;t perform any permission checks, meaning it should be annotated with @RequiresNoPermission."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/supervision/java/com/android/server/supervision/SupervisionService.java"
+ line="45"
+ column="5"/>
+ </issue>
+
+</issues>
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
new file mode 100644
index 000000000000..2c2e5fdb68d9
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_system_performance",
+}
+
+android_test {
+ name: "DynamicInstrumentationManagerServiceTests",
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "hamcrest-library",
+ "platform-test-annotations",
+ "services.core",
+ "testables",
+ "truth",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ ],
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
new file mode 100644
index 000000000000..4913d706a72d
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.os.instrumentation" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.os.instrumentation"
+ android:label="DynamicInstrumentationmanagerService Unit Tests"/>
+</manifest> \ No newline at end of file
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
new file mode 100644
index 000000000000..33defed0eae6
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "DynamicInstrumentationManagerServiceTests"
+ }
+ ]
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
new file mode 100644
index 000000000000..04073fab2059
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public abstract class TestAbstractClass {
+ abstract void abstractMethod();
+
+ void concreteMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
new file mode 100644
index 000000000000..2c25e7a52f73
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestAbstractClassImpl extends TestAbstractClass {
+ @Override
+ void abstractMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
new file mode 100644
index 000000000000..085f5953f0e5
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestClass {
+ void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g,
+ short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) {
+ }
+
+ void classParams(String a, String[] b) {
+ }
+
+ private void privateMethod() {
+ }
+
+ /**
+ * docs!
+ */
+ public void publicMethod() {
+ }
+
+ private static class InnerClass {
+ private void innerMethod(InnerClass arg, InnerClass[] argArray) {
+ }
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
new file mode 100644
index 000000000000..7af4f254ab1c
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+/**
+ * docs!
+ */
+public interface TestInterface {
+ /**
+ * docs!
+ */
+ void interfaceMethod();
+
+ /**
+ * docs!
+ */
+ default void defaultMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
new file mode 100644
index 000000000000..53aecbc08939
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestInterfaceImpl implements TestInterface {
+ @Override
+ public void interfaceMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
new file mode 100644
index 000000000000..6e14bad11837
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os.instrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.MethodDescriptorParser;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.TestAbstractClass;
+import com.android.server.TestAbstractClassImpl;
+import com.android.server.TestClass;
+import com.android.server.TestInterface;
+import com.android.server.TestInterfaceImpl;
+
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Test class for
+ * {@link MethodDescriptorParser#parseMethodDescriptor(ClassLoader,
+ * MethodDescriptor)}.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:ParseMethodDescriptorTest
+ */
+@Presubmit
+@SmallTest
+public class ParseMethodDescriptorTest {
+ private static final String[] PRIMITIVE_PARAMS = new String[]{
+ "boolean", "boolean[]", "byte", "byte[]", "char", "char[]", "short", "short[]", "int",
+ "int[]", "long", "long[]", "float", "float[]", "double", "double[]"};
+ private static final String[] CLASS_PARAMS =
+ new String[]{"java.lang.String", "java.lang.String[]"};
+
+ @Test
+ public void primitiveParams() {
+ assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+ PRIMITIVE_PARAMS));
+ }
+
+ @Test
+ public void classParams() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "classParams", CLASS_PARAMS));
+ }
+
+ @Test
+ public void publicMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "publicMethod"));
+ }
+
+ @Test
+ public void privateMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "privateMethod"));
+ }
+
+ @Test
+ public void innerClass() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName() + "$InnerClass", "innerMethod",
+ new String[]{TestClass.class.getName() + "$InnerClass",
+ TestClass.class.getName() + "$InnerClass[]"}));
+ }
+
+ @Test
+ public void interface_concreteMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestInterfaceImpl.class.getName(), "interfaceMethod"));
+ }
+
+ @Test
+ public void interface_defaultMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestInterface.class.getName(), "defaultMethod"));
+ }
+
+ @Test
+ public void abstractClassImpl_abstractMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestAbstractClassImpl.class.getName(), "abstractMethod"));
+ }
+
+ @Test
+ public void abstractClass_concreteMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestAbstractClass.class.getName(), "concreteMethod"));
+ }
+
+ @Test
+ public void notFound_illegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> parseMethodDescriptor("foo", "bar"));
+ assertThrows(IllegalArgumentException.class,
+ () -> parseMethodDescriptor(TestClass.class.getName(), "bar"));
+ assertThrows(IllegalArgumentException.class,
+ () -> parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+ new String[]{"int"}));
+ }
+
+ private Method parseMethodDescriptor(String fqcn, String methodName) {
+ return MethodDescriptorParser.parseMethodDescriptor(
+ getClass().getClassLoader(),
+ getMethodDescriptor(fqcn, methodName, new String[]{}));
+ }
+
+ private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) {
+ return MethodDescriptorParser.parseMethodDescriptor(
+ getClass().getClassLoader(),
+ getMethodDescriptor(fqcn, methodName, fqParameters));
+ }
+
+ private MethodDescriptor getMethodDescriptor(String fqcn, String methodName,
+ String[] fqParameters) {
+ MethodDescriptor methodDescriptor = new MethodDescriptor();
+ methodDescriptor.fullyQualifiedClassName = fqcn;
+ methodDescriptor.methodName = methodName;
+ methodDescriptor.fullyQualifiedParameters = fqParameters;
+ return methodDescriptor;
+ }
+
+
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 2c785049412a..d08715586580 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -85,6 +85,8 @@ public class InputMethodServiceTest {
private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
"settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
+ private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
+
private Instrumentation mInstrumentation;
private UiDevice mUiDevice;
private Context mContext;
@@ -95,7 +97,7 @@ public class InputMethodServiceTest {
private boolean mShowImeWithHardKeyboardEnabled;
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = new CheckFlagsRule(mFlagsValueProvider);
@Before
public void setUp() throws Exception {
@@ -159,15 +161,16 @@ public class InputMethodServiceTest {
// Press home key to hide soft keyboard.
Log.i(TAG, "Press home");
- verifyInputViewStatus(
- () -> assertThat(mUiDevice.pressHome()).isTrue(),
- true /* expected */,
- false /* inputViewStarted */);
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ assertThat(mUiDevice.pressHome()).isTrue();
// The IME visibility is only sent at the end of the animation. Therefore, we have to
// wait until the visibility was sent to the server and the IME window hidden.
eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
} else {
+ verifyInputViewStatus(
+ () -> assertThat(mUiDevice.pressHome()).isTrue(),
+ true /* expected */,
+ false /* inputViewStarted */);
assertThat(mInputMethodService.isInputViewShown()).isFalse();
}
}
@@ -191,7 +194,13 @@ public class InputMethodServiceTest {
() -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(),
true /* expected */,
false /* inputViewStarted */);
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -773,7 +782,7 @@ public class InputMethodServiceTest {
backButtonUiObject.click();
mInstrumentation.waitForIdleSync();
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
// The IME visibility is only sent at the end of the animation. Therefore, we have to
// wait until the visibility was sent to the server and the IME window hidden.
eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
@@ -811,7 +820,7 @@ public class InputMethodServiceTest {
backButtonUiObject.longClick();
mInstrumentation.waitForIdleSync();
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
// The IME visibility is only sent at the end of the animation. Therefore, we have to
// wait until the visibility was sent to the server and the IME window hidden.
eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
@@ -899,7 +908,7 @@ public class InputMethodServiceTest {
assertWithMessage("Input Method Switcher Menu is shown")
.that(isInputMethodPickerShown(imm))
.isTrue();
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
// The IME visibility is only sent at the end of the animation. Therefore, we have to
// wait until the visibility was sent to the server and the IME window hidden.
eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 6afcae797277..3aeab0980b22 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -76,8 +76,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase {
+ private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
+
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = new CheckFlagsRule(mFlagsValueProvider);
private DefaultImeVisibilityApplier mVisibilityApplier;
@Before
@@ -151,7 +153,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, false /* invoked */);
verifySetImeVisibility(false /* setVisible */, true /* invoked */);
} else {
@@ -168,7 +170,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, false /* invoked */);
verifySetImeVisibility(false /* setVisible */, true /* invoked */);
} else {
@@ -182,7 +184,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_SHOW_IME_IMPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
- if (Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, true /* invoked */);
verifySetImeVisibility(false /* setVisible */, false /* invoked */);
} else {
@@ -260,7 +262,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
verify(mVisibilityApplier).applyImeVisibility(
eq(mWindowToken), any(), eq(STATE_HIDE_IME),
eq(SoftInputShowHideReason.NOT_SET), eq(mUserId) /* userId */);
- if (!Flags.refactorInsetsController()) {
+ if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(eq(mWindowToken),
eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index e5d315358df6..72cbac331551 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -35,6 +35,7 @@ import java.io.StringWriter;
public final class InputMethodManagerServiceTests {
static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2;
static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3;
+ private static final int TEST_IME_USER_ID = 1;
static InputMethodManagerService.ImeDisplayValidator sChecker =
(displayId) -> {
@@ -102,7 +103,8 @@ public final class InputMethodManagerServiceTests {
null,
null,
null,
- null));
+ null,
+ TEST_IME_USER_ID));
history.dump(new PrintWriter(writer), "" /* prefix */);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index 4d956b2df273..c958bd339267 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -39,8 +39,10 @@ import static org.mockito.Mockito.when;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.RemoteException;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.Flags;
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodClient;
@@ -89,6 +91,9 @@ public class InputMethodManagerServiceWindowGainedFocusTest
};
private static final int DEFAULT_SOFT_INPUT_FLAG =
StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR;
+
+ private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
+
@Mock
VirtualDeviceManagerInternal mMockVdmInternal;
@@ -125,7 +130,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
case SOFT_INPUT_STATE_UNSPECIFIED:
boolean showSoftInput =
(mSoftInputAdjustment == SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen;
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, showSoftInput /* invoked */);
// A hide can only be triggered if there is no editorFocused, which this test
// always sets.
@@ -141,7 +146,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
break;
case SOFT_INPUT_STATE_VISIBLE:
case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, true /* invoked */);
verifySetImeVisibility(false /* setVisible */, false /* invoked */);
} else {
@@ -150,7 +155,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
}
break;
case SOFT_INPUT_STATE_UNCHANGED:
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, false /* invoked */);
verifySetImeVisibility(false /* setVisible */, false /* invoked */);
} else {
@@ -160,7 +165,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
break;
case SOFT_INPUT_STATE_HIDDEN:
case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, false /* invoked */);
// In this case, we don't have to manipulate the requested visible types of
// the WindowState, as they're already in the correct state
@@ -192,7 +197,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
case SOFT_INPUT_STATE_UNSPECIFIED:
boolean hideSoftInput =
(mSoftInputAdjustment != SOFT_INPUT_ADJUST_RESIZE) && !mIsLargeScreen;
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
// A show can only be triggered in forward navigation
verifySetImeVisibility(false /* setVisible */, false /* invoked */);
// A hide can only be triggered if there is no editorFocused, which this test
@@ -209,7 +214,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
case SOFT_INPUT_STATE_VISIBLE:
case SOFT_INPUT_STATE_HIDDEN:
case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, false /* invoked */);
verifySetImeVisibility(false /* setVisible */, false /* invoked */);
} else {
@@ -218,7 +223,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
}
break;
case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, true /* invoked */);
verifySetImeVisibility(false /* setVisible */, false /* invoked */);
} else {
@@ -227,7 +232,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
}
break;
case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
verifySetImeVisibility(true /* setVisible */, false /* invoked */);
// In this case, we don't have to manipulate the requested visible types of
// the WindowState, as they're already in the correct state
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMenuControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMenuControllerTest.java
new file mode 100644
index 000000000000..02dc86bffe2d
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMenuControllerTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.server.inputmethod.InputMethodMenuControllerNew.getMenuItems;
+import static com.android.server.inputmethod.InputMethodMenuControllerNew.getSelectedIndex;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingControllerTest.addTestImeSubtypeListItems;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.inputmethod.Flags;
+
+import com.android.server.inputmethod.InputMethodMenuControllerNew.DividerItem;
+import com.android.server.inputmethod.InputMethodMenuControllerNew.HeaderItem;
+import com.android.server.inputmethod.InputMethodMenuControllerNew.SubtypeItem;
+import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+public class InputMethodMenuControllerTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /** Verifies that getMenuItems maintains the same order and information from the given items. */
+ @Test
+ public void testGetMenuItems() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var menuItems = getMenuItems(items);
+
+ int itemsIndex = 0;
+
+ for (int i = 0; i < menuItems.size(); i++) {
+ final var menuItem = menuItems.get(i);
+ if (menuItem instanceof SubtypeItem subtypeItem) {
+ final var item = items.get(itemsIndex);
+
+ assertWithMessage("IME name does not match").that(subtypeItem.mImeName)
+ .isEqualTo(item.mImeName);
+ assertWithMessage("Subtype name does not match").that(subtypeItem.mSubtypeName)
+ .isEqualTo(item.mSubtypeName);
+ assertWithMessage("InputMethodInfo does not match").that(subtypeItem.mImi)
+ .isEqualTo(item.mImi);
+ assertWithMessage("Subtype index does not match").that(subtypeItem.mSubtypeIndex)
+ .isEqualTo(item.mSubtypeIndex);
+
+ itemsIndex++;
+ }
+ }
+
+ assertWithMessage("Items list was not fully traversed").that(itemsIndex)
+ .isEqualTo(items.size());
+ }
+
+ /**
+ * Verifies that getMenuItems does not add a header or divider if all the items belong to
+ * a single input method.
+ */
+ @Test
+ public void testGetMenuItemsNoHeaderOrDividerForSingleInputMethod() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var menuItems = getMenuItems(items);
+
+ assertThat(menuItems.stream()
+ .filter(item -> item instanceof HeaderItem || item instanceof DividerItem).toList())
+ .isEmpty();
+ }
+
+ /**
+ * Verifies that getMenuItems only adds headers for item groups with at least two items,
+ * or with a single item with a subtype name.
+ */
+ @Test
+ public void testGetMenuItemsHeaders() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "DefaultIme", "DefaultIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "ItalianIme", "ItalianIme",
+ List.of("it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var menuItems = getMenuItems(items);
+
+ assertWithMessage("Must have menu items").that(menuItems).isNotEmpty();
+
+ final var headersAndDividers = menuItems.stream()
+ .filter(item -> item instanceof HeaderItem || item instanceof DividerItem)
+ .toList();
+
+ assertWithMessage("Must have header and divider items").that(headersAndDividers).hasSize(5);
+
+ assertWithMessage("First group has no header")
+ .that(menuItems.getFirst()).isInstanceOf(SubtypeItem.class);
+ assertWithMessage("Group with multiple items has divider")
+ .that(headersAndDividers.get(0)).isInstanceOf(DividerItem.class);
+ assertWithMessage("Group with multiple items has header")
+ .that(headersAndDividers.get(1)).isInstanceOf(HeaderItem.class);
+ assertWithMessage("Group with single item with subtype name has divider")
+ .that(headersAndDividers.get(2)).isInstanceOf(DividerItem.class);
+ assertWithMessage("Group with single item with subtype name has header")
+ .that(headersAndDividers.get(3)).isInstanceOf(HeaderItem.class);
+ assertWithMessage("Group with single item without subtype name has divider only")
+ .that(headersAndDividers.get(4)).isInstanceOf(DividerItem.class);
+ }
+
+ /** Verifies that getMenuItems adds a divider before every header except the first one. */
+ @Test
+ public void testGetMenuItemsDivider() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "ItalianIme", "ItalianIme",
+ List.of("it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var menuItems = getMenuItems(items);
+
+ assertWithMessage("First item is a header")
+ .that(menuItems.getFirst()).isInstanceOf(HeaderItem.class);
+ assertWithMessage("Last item is a subtype")
+ .that(menuItems.getLast()).isInstanceOf(SubtypeItem.class);
+
+ for (int i = 0; i < menuItems.size(); i++) {
+ final var item = menuItems.get(i);
+ if (item instanceof HeaderItem && i > 0) {
+ final var prevItem = menuItems.get(i - 1);
+ assertWithMessage("The item before a header should be a divider")
+ .that(prevItem).isInstanceOf(DividerItem.class);
+ } else if (item instanceof DividerItem && i < menuItems.size() - 1) {
+ final var nextItem = menuItems.get(i + 1);
+ assertWithMessage("The item after a divider should be a header or subtype")
+ .that(nextItem instanceof HeaderItem || nextItem instanceof SubtypeItem)
+ .isTrue();
+ }
+ }
+ }
+
+ /**
+ * Verifies that getSelectedIndex returns the matching item when the selected subtype is given.
+ */
+ @Test
+ public void testGetSelectedIndexWithSelectedSubtype() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ List.of("it", "jp", "pt"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var simpleImeId = items.get(2).mImi.getId();
+ final var menuItems = getMenuItems(items);
+
+ final int selectedIndex = getSelectedIndex(menuItems, simpleImeId, 1);
+ // Two headers + one divider + three items
+ assertThat(selectedIndex).isEqualTo(6);
+ }
+
+ /**
+ * Verifies that getSelectedIndex returns the first item of the selected input method,
+ * when no selected subtype is given.
+ */
+ @Test
+ public void testGetSelectedIndexWithoutSelectedSubtype() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ List.of("it", "jp", "pt"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var simpleImeId = items.get(2).mImi.getId();
+ final var menuItems = getMenuItems(items);
+
+ final int selectedIndex = getSelectedIndex(menuItems, simpleImeId, NOT_A_SUBTYPE_INDEX);
+
+ // Two headers + one divider + two items
+ assertThat(selectedIndex).isEqualTo(5);
+ }
+
+ /**
+ * Verifies that getSelectedIndex will return the item of the selected input method that has
+ * no subtype, when this is the first one reached, regardless of the given selected subtype.
+ */
+ @Test
+ public void getSelectedIndexNoSubtype() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var simpleImeId = items.get(2).mImi.getId();
+ final var menuItems = getMenuItems(items);
+
+ final int selectedIndex = getSelectedIndex(menuItems, simpleImeId, 1);
+
+ // One header + one divider + two items
+ assertThat(selectedIndex).isEqualTo(4);
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 770451cc838d..c30ab738b098 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -75,7 +75,7 @@ public final class InputMethodSubtypeSwitchingControllerTest {
.build();
}
- private static void addTestImeSubtypeListItems(@NonNull List<ImeSubtypeListItem> items,
+ static void addTestImeSubtypeListItems(@NonNull List<ImeSubtypeListItem> items,
@NonNull String imeName, @NonNull String imeLabel,
@Nullable List<String> subtypeLocales, boolean supportsSwitchingToNextInputMethod) {
final ApplicationInfo ai = new ApplicationInfo();
@@ -101,13 +101,13 @@ public final class InputMethodSubtypeSwitchingControllerTest {
TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
if (subtypes == null) {
- items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
- NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
+ items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */,
+ imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
} else {
for (int i = 0; i < subtypes.size(); ++i) {
final String subtypeLocale = subtypeLocales.get(i);
- items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
- SYSTEM_LOCALE));
+ items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */,
+ imi, i, subtypeLocale, SYSTEM_LOCALE));
}
}
}
@@ -138,8 +138,8 @@ public final class InputMethodSubtypeSwitchingControllerTest {
final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME,
TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME);
- return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
- SYSTEM_LOCALE);
+ return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi,
+ subtypeIndex, subtypeLocale, SYSTEM_LOCALE);
}
@NonNull
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 5c4716dc751e..7d5532f6e401 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -57,6 +57,7 @@ import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doReturn
@@ -383,6 +384,10 @@ class PackageManagerComponentLabelIconOverrideTest {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
PackageManager.PERMISSION_GRANTED
}
+ whenever(this.checkPermission(
+ eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) {
+ PackageManager.PERMISSION_GRANTED
+ }
}
val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
whenever(this.snapshot()) { this@mock }
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index f15e533fee2b..2f00a1bb3c8c 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -32,6 +32,7 @@ android_test {
"androidx.test.runner",
"truth",
"Harrier",
+ "bedstead-multiuser",
],
platform_apis: true,
certificate: "platform",
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
index 4012d8e4af96..9f02b3fe4033 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
@@ -33,6 +33,7 @@ import android.media.projection.MediaProjectionManager;
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.view.Display;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -174,7 +175,8 @@ public class AppEnumerationInternalTests {
ServiceManager.getService(MEDIA_PROJECTION_SERVICE));
assertThat(mediaProjectionManager.createProjection(0 /* uid */, TARGET_SHARED_USER,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */))
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */,
+ Display.DEFAULT_DISPLAY /* displayId */))
.isNotNull();
}
@@ -187,7 +189,8 @@ public class AppEnumerationInternalTests {
Assert.assertThrows(IllegalArgumentException.class,
() -> mediaProjectionManager.createProjection(0 /* uid */, TARGET_SHARED_USER,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */));
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */,
+ Display.DEFAULT_DISPLAY /* displayId */));
}
private static void installPackage(String apkPath, boolean forceQueryable) {
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca7775e22..48cebd7dcb04 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -22,6 +22,7 @@ import static android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS;
import static android.Manifest.permission.MOVE_PACKAGE;
import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.secondaryUser;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
@@ -45,7 +46,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.nene.users.UserReference;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.TestUtils;
@@ -112,9 +113,9 @@ public class CrossUserPackageVisibilityTests {
final UserReference primaryUser = sDeviceState.primaryUser();
if (primaryUser.id() == UserHandle.myUserId()) {
mCurrentUser = primaryUser;
- mOtherUser = sDeviceState.secondaryUser();
+ mOtherUser = secondaryUser(sDeviceState);
} else {
- mCurrentUser = sDeviceState.secondaryUser();
+ mCurrentUser = secondaryUser(sDeviceState);
mOtherUser = primaryUser;
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
new file mode 100644
index 000000000000..0ae7699aeb71
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.Flags.FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeNonSdkSandbox;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@AppModeFull
+@AppModeNonSdkSandbox
+@RunWith(AndroidJUnit4.class)
+public class BroadcastHelperTest {
+ private static final String TAG = "BroadcastHelperTest";
+ private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename";
+ private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY =
+ PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity";
+ private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
+ "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Mock
+ ActivityManagerInternal mMockActivityManagerInternal;
+ @Mock
+ AndroidPackageInternal mMockAndroidPackageInternal;
+ @Mock
+ Computer mMockSnapshot;
+ @Mock
+ Handler mMockHandler;
+ @Mock
+ PackageManagerServiceInjector mMockPackageManagerServiceInjector;
+ @Mock
+ PackageMonitorCallbackHelper mMockPackageMonitorCallbackHelper;
+ @Mock
+ PackageStateInternal mMockPackageStateInternal;
+ @Mock
+ ParsedActivity mMockParsedActivity;
+ @Mock
+ UserManagerInternal mMockUserManagerInternal;
+
+ private Context mContext;
+ private BroadcastHelper mBroadcastHelper;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ when(mMockHandler.sendMessageAtTime(any(Message.class), anyLong())).thenAnswer(
+ i -> {
+ ((Message) i.getArguments()[0]).getCallback().run();
+ return true;
+ });
+ when(mMockPackageManagerServiceInjector.getActivityManagerInternal()).thenReturn(
+ mMockActivityManagerInternal);
+ when(mMockPackageManagerServiceInjector.getContext()).thenReturn(mContext);
+ when(mMockPackageManagerServiceInjector.getHandler()).thenReturn(mMockHandler);
+ when(mMockPackageManagerServiceInjector.getPackageMonitorCallbackHelper()).thenReturn(
+ mMockPackageMonitorCallbackHelper);
+ when(mMockPackageManagerServiceInjector.getUserManagerInternal()).thenReturn(
+ mMockUserManagerInternal);
+
+ mBroadcastHelper = new BroadcastHelper(mMockPackageManagerServiceInjector);
+ }
+
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
+ captor.capture(), eq(null),
+ eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}),
+ anyInt(), eq(null), eq(null), eq(null));
+ Intent intent = captor.getValue();
+ assertNotNull(intent);
+ assertThat(intent.getPackage()).isEqualTo("android");
+ }
+
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
+ eq(null), anyInt(), eq(null), eq(null), eq(null));
+ Intent intent = captor.getValue();
+ assertNotNull(intent);
+ assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ }
+
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSharedUserIdApplications()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[]{"shared.package"} /* sharedPackages */);
+
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
+ String[].class);
+ verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
+ captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
+ eq(null), eq(null), eq(null));
+ List<Intent> intents = captorIntent.getAllValues();
+ List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
+ assertNotNull(intents);
+ assertThat(intents.size()).isEqualTo(3);
+
+ final Intent intent1 = intents.get(0);
+ final String[] requiredPermission1 = requiredPermissions.get(0);
+ assertThat(intent1.getPackage()).isEqualTo("android");
+ assertThat(requiredPermission1).isEqualTo(
+ new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+
+ final Intent intent2 = intents.get(1);
+ final String[] requiredPermission2 = requiredPermissions.get(1);
+ assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ assertThat(requiredPermission2).isNull();
+
+ final Intent intent3 = intents.get(2);
+ final String[] requiredPermission3 = requiredPermissions.get(2);
+ assertThat(intent3.getPackage()).isEqualTo("shared.package");
+ assertThat(requiredPermission3).isNull();
+ }
+
+ @Test
+ public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
+ eq(null), anyInt(), eq(null), eq(null), eq(null));
+ Intent intent = captor.getValue();
+ assertNotNull(intent);
+ assertNull(intent.getPackage());
+ }
+
+ private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent,
+ String[] sharedPackages) {
+ when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+ anyInt())).thenReturn(mMockPackageStateInternal);
+ when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false);
+ when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null);
+ when(mMockSnapshot.getSharedUserPackagesForPackage(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+ anyInt())).thenReturn(sharedPackages);
+ when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal);
+
+ when(mMockParsedActivity.getClassName()).thenReturn(
+ PACKAGE_CHANGED_TEST_MAIN_ACTIVITY);
+ when(mMockParsedActivity.isExported()).thenReturn(changeExportedComponent);
+ ArrayList<ParsedActivity> parsedActivities = new ArrayList<>();
+ parsedActivities.add(mMockParsedActivity);
+
+ when(mMockAndroidPackageInternal.getReceivers()).thenReturn(new ArrayList<>());
+ when(mMockAndroidPackageInternal.getProviders()).thenReturn(new ArrayList<>());
+ when(mMockAndroidPackageInternal.getServices()).thenReturn(new ArrayList<>());
+ when(mMockAndroidPackageInternal.getActivities()).thenReturn(parsedActivities);
+
+ doNothing().when(mMockPackageMonitorCallbackHelper).notifyPackageChanged(any(),
+ anyBoolean(), any(), anyInt(), any(), any(), any(), any(), any());
+ when(mMockActivityManagerInternal.broadcastIntentWithCallback(any(), any(), any(), anyInt(),
+ any(), any(), any())).thenReturn(ActivityManager.BROADCAST_SUCCESS);
+
+ ArrayList<String> componentNames = new ArrayList<>();
+ componentNames.add(PACKAGE_CHANGED_TEST_MAIN_ACTIVITY);
+
+ mBroadcastHelper.sendPackageChangedBroadcast(mMockSnapshot,
+ PACKAGE_CHANGED_TEST_PACKAGE_NAME, true /* dontKillApp */, componentNames,
+ UserHandle.USER_SYSTEM, "test" /* reason */, "test" /* reasonForTrace */);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 7aa2ff50dbbd..14dce103fc90 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -195,7 +195,8 @@ class PackageInstallerSessionTest {
/* isApplied */ false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
/* stagedSessionErrorMessage */ "some error",
- /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
+ /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
+ /* installDependencyHelper */ null
)
}
@@ -249,7 +250,8 @@ class PackageInstallerSessionTest {
mock(StagingManager::class.java),
mTmpDir,
mock(PackageSessionProvider::class.java),
- mock(SilentUpdatePolicy::class.java)
+ mock(SilentUpdatePolicy::class.java),
+ mock(InstallDependencyHelper::class.java)
)
ret.add(session)
} catch (e: Exception) {
@@ -343,4 +345,4 @@ class PackageInstallerSessionTest {
assertThat(expected.mInitiatingPackageName).isEqualTo(actual.mInitiatingPackageName)
assertThat(expected.mOriginatingPackageName).isEqualTo(actual.mOriginatingPackageName)
}
-} \ No newline at end of file
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 5da202f109d4..f5c0de034483 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -20,6 +20,7 @@ import static android.content.UriRelativeFilter.QUERY;
import static android.content.UriRelativeFilter.FRAGMENT;
import static android.content.UriRelativeFilterGroup.ACTION_ALLOW;
import static android.content.UriRelativeFilterGroup.ACTION_BLOCK;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS;
import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB;
import static android.os.PatternMatcher.PATTERN_LITERAL;
import static android.os.PatternMatcher.PATTERN_PREFIX;
@@ -111,6 +112,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.google.android.collect.Sets;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -212,6 +215,30 @@ public class PackageParserTest {
}
@Test
+ public void testParse_withCache_hiddenApiAllowlist() throws Exception {
+ CachePackageNameParser pp = new CachePackageNameParser(null);
+
+ pp.setCacheDir(mTmpDir);
+ // The first parse will write this package to the cache.
+ pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */);
+
+ // Now attempt to parse the package again, should return the
+ // cached result.
+ ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
+ true /* useCaches */);
+ assertEquals("cache_android", pkg.getPackageName());
+
+ // Create application info
+ pkg.hideAsFinal();
+ ApplicationInfo aInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
+ PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(pkg));
+
+ // verify ext flag for hidden APIs allowlist
+ assertEquals(PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
+ aInfo.privateFlagsExt & PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
+ }
+
+ @Test
public void test_serializePackage() throws Exception {
try (PackageParser2 pp = PackageParserUtils.forParsingFileWithDefaults()) {
AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
@@ -856,35 +883,37 @@ public class PackageParserTest {
*/
public static class CachePackageNameParser extends PackageParser2 {
- CachePackageNameParser(@Nullable File cacheDir) {
- super(null, null, null, new Callback() {
- @Override
- public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
- return true;
- }
+ private static final Callback CALLBACK = new Callback() {
+ @Override
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ return true;
+ }
- @Override
- public boolean hasFeature(String feature) {
- return false;
- }
+ @Override
+ public boolean hasFeature(String feature) {
+ return false;
+ }
- @Override
- public Set<String> getHiddenApiWhitelistedApps() {
- return new ArraySet<>();
- }
+ @Override
+ public Set<String> getHiddenApiWhitelistedApps() {
+ return Sets.newArraySet("cache_android");
+ }
- @Override
- public Set<String> getInstallConstraintsAllowlist() {
- return new ArraySet<>();
- }
- });
+ @Override
+ public Set<String> getInstallConstraintsAllowlist() {
+ return new ArraySet<>();
+ }
+ };
+
+ CachePackageNameParser(@Nullable File cacheDir) {
+ super(null, null, null, CALLBACK);
if (cacheDir != null) {
setCacheDir(cacheDir);
}
}
void setCacheDir(@NonNull File cacheDir) {
- this.mCacher = new PackageCacher(cacheDir) {
+ this.mCacher = new PackageCacher(cacheDir, CALLBACK) {
@Override
public ParsedPackage fromCacheEntry(byte[] cacheEntry) {
ParsedPackage parsed = super.fromCacheEntry(cacheEntry);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index c1271bb0ee36..9a61492971a5 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -511,7 +511,7 @@ public class ScanTests {
.addUsesPermission(
new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
- final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
+ final ScanResult scanResult = ScanPackageUtils.scanPackageOnly(
createBasicScanRequestBuilder(basicPackage).build(),
mMockInjector,
true /*isUnderFactoryTest*/,
@@ -559,7 +559,7 @@ public class ScanTests {
private ScanResult executeScan(
ScanRequest scanRequest) throws PackageManagerException {
- ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
+ ScanResult result = ScanPackageUtils.scanPackageOnly(
scanRequest,
mMockInjector,
false /*isUnderFactoryTest*/,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index d4b57f191ecd..31f03704a756 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -26,6 +26,8 @@ import android.content.pm.SigningDetails
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.util.ArraySet
import android.util.SparseArray
import android.util.SparseIntArray
@@ -47,14 +49,19 @@ import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
+import org.junit.Rule
import java.security.KeyPairGenerator
import java.security.PublicKey
import java.util.UUID
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
+@EnableFlags(android.content.pm.Flags.FLAG_INCLUDE_FEATURE_FLAGS_IN_PACKAGE_CACHER)
class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
+ @get:Rule
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
companion object {
private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
}
@@ -93,6 +100,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
"getUsesOptionalLibrariesSorted",
"getUsesSdkLibrariesSorted",
"getUsesStaticLibrariesSorted",
+ "readFeatureFlagState",
+ "writeFeatureFlagState",
// Tested through setting minor/major manually
"setLongVersionCode",
"getLongVersionCode",
@@ -149,6 +158,10 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
"isSystem",
"isSystemExt",
"isVendor",
+
+ // Tested through addFeatureFlag
+ "addFeatureFlag",
+ "getFeatureFlagState",
)
override val baseParams = listOf(
@@ -275,6 +288,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::isUpdatableSystem,
AndroidPackage::getEmergencyInstaller,
AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
+ AndroidPackage::getIntentMatchingFlags,
+ AndroidPackage::getPageSizeAppCompatFlags,
)
override fun extraParams() = listOf(
@@ -568,6 +583,34 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
PackageImpl::setSystemExt,
true
),
+ getSetByValue(
+ AndroidPackage::getAlternateLauncherIconResIds,
+ PackageImpl::setAlternateLauncherIconResIds,
+ intArrayOf(3, 5, 7),
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it.size },
+ { it[0] },
+ { it[1] },
+ { it[2] }
+ )
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getAlternateLauncherLabelResIds,
+ PackageImpl::setAlternateLauncherLabelResIds,
+ intArrayOf(3, 5, 7),
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it.size },
+ { it[0] },
+ { it[1] },
+ { it[2] }
+ )
+ }
+ ),
)
override fun initialObject() = PackageImpl.forParsing(
@@ -613,6 +656,9 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
.setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
.addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true)
.addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
+ .addFeatureFlag("testFlag1", null)
+ .addFeatureFlag("testFlag2", true)
+ .addFeatureFlag("testFlag3", false)
override fun finalizeObject(parcelable: Parcelable) {
(parcelable as PackageImpl).hideAsParsed().hideAsFinal()
@@ -673,6 +719,12 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
.containsExactly("testCertDigest2")
expect.that(after.storageUuid).isEqualTo(TEST_UUID)
+
+ expect.that(after.featureFlagState).containsExactlyEntriesIn(mapOf(
+ "testFlag1" to null,
+ "testFlag2" to true,
+ "testFlag3" to false,
+ ))
}
private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 349b83167793..8782b1ad1f39 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -55,7 +55,8 @@ class ParsedActivityTest : ParsedMainComponentTest(
ParsedActivity::getUiOptions,
ParsedActivity::isSupportsSizeChanges,
ParsedActivity::getRequiredDisplayCategory,
- ParsedActivity::getRequireContentUriPermissionFromCaller
+ ParsedActivity::getRequireContentUriPermissionFromCaller,
+ ParsedActivity::getIntentMatchingFlags,
)
override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 1e844705fe3c..3e349795c60a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -38,6 +38,7 @@ class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, Parsed
ParsedProvider::isForceUriPermissions,
ParsedProvider::isMultiProcess,
ParsedProvider::getInitOrder,
+ ParsedProvider::getIntentMatchingFlags,
)
override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
index 79d5a4f7030a..694db47140c7 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -30,5 +30,6 @@ class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class, ParsedSe
override val mainComponentSubclassBaseParams = listOf(
ParsedService::getForegroundServiceType,
ParsedService::getPermission,
+ ParsedService::getIntentMatchingFlags,
)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index b21c34905bad..2144785ed8fd 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -94,6 +94,7 @@ class PackageStateTest {
ParsedService::getIntents,
ParsedService::getProperties,
Intent::getCategories,
+ Intent::getExtraIntentKeys,
PackageUserState::getDisabledComponents,
PackageUserState::getEnabledComponents,
PackageUserState::getSharedLibraryOverlayPaths,
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
index 9560ec9990ad..836f90b992d6 100644
--- a/services/tests/appfunctions/Android.bp
+++ b/services/tests/appfunctions/Android.bp
@@ -36,6 +36,9 @@ android_test {
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
+ "androidx.core_core-ktx",
+ "kotlin-test",
+ "kotlinx_coroutines_test",
"platform-test-annotations",
"services.appfunctions",
"servicestests-core-utils",
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
index da3e94f64e56..d3262046cad8 100644
--- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
@@ -15,8 +15,12 @@
*/
package android.app.appfunctions
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED
import android.app.appsearch.AppSearchSchema
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -108,32 +112,43 @@ class AppFunctionRuntimeMetadataTest {
assertThat(runtimeMetadata.packageName).isEqualTo("com.pkg")
assertThat(runtimeMetadata.functionId).isEqualTo("funcId")
- assertThat(runtimeMetadata.enabled).isNull()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId)
.isEqualTo("android\$apps-db/app_functions#com.pkg/funcId")
}
@Test
- fun setEnabled_true() {
+ fun setEnabled_enabled() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(true).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(APP_FUNCTION_STATE_ENABLED).build()
- assertThat(runtimeMetadata.enabled).isTrue()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_ENABLED)
}
@Test
- fun setEnabled_false() {
+ fun setEnabled_disabled() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(false).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+ APP_FUNCTION_STATE_DISABLED).build()
- assertThat(runtimeMetadata.enabled).isFalse()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DISABLED)
}
@Test
- fun setEnabled_null() {
+ fun setEnabled_default() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(null).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+ APP_FUNCTION_STATE_DEFAULT).build()
- assertThat(runtimeMetadata.enabled).isNull()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
+ }
+
+ @Test
+ fun setEnabled_illegalArgument() {
+ val runtimeMetadataBuilder =
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId")
+ assertFailsWith<IllegalArgumentException>("Value of EnabledState is unsupported.") {
+ runtimeMetadataBuilder.setEnabled(-1)
+ }
}
}
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt
new file mode 100644
index 000000000000..413eb314c41d
--- /dev/null
+++ b/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions
+
+import android.app.appsearch.GenericDocument
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+
+@RunWith(JUnit4::class)
+class GenericDocumentWrapperTest {
+
+ @Test
+ fun parcelUnparcel() {
+ val doc =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("test", 42)
+ .build()
+ val wrapper = GenericDocumentWrapper(doc)
+
+ val recovered = parcelUnparcel(wrapper)
+
+ assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42)
+ }
+
+ @Test
+ fun parcelUnparcel_afterGetValue() {
+ val doc =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("test", 42)
+ .build()
+ val wrapper = GenericDocumentWrapper(doc)
+ assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42)
+
+ val recovered = parcelUnparcel(wrapper)
+
+ assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42)
+ }
+
+
+ @Test
+ fun getValue() {
+ val doc =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("test", 42)
+ .build()
+ val wrapper = GenericDocumentWrapper(doc)
+
+ assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42)
+ }
+
+ private fun parcelUnparcel(obj: GenericDocumentWrapper): GenericDocumentWrapper {
+ val parcel = Parcel.obtain()
+ try {
+ obj.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return GenericDocumentWrapper.CREATOR.createFromParcel(parcel)
+ } finally {
+ parcel.recycle()
+ }
+ }
+} \ No newline at end of file
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt
new file mode 100644
index 000000000000..a69e9025bfa0
--- /dev/null
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions
+
+import android.app.appfunctions.flags.Flags
+import android.content.Context
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@RequiresFlagsEnabled(Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+class AppFunctionManagerServiceImplTest {
+ @get:Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val context: Context
+ get() = ApplicationProvider.getApplicationContext()
+
+ private val serviceImpl = AppFunctionManagerServiceImpl(context)
+
+ @Test
+ fun testGetLockForPackage_samePackage() {
+ val packageName = "com.example.app"
+ val lock1 = serviceImpl.getLockForPackage(packageName)
+ val lock2 = serviceImpl.getLockForPackage(packageName)
+
+ // Assert that the same lock object is returned for the same package name
+ assertThat(lock1).isEqualTo(lock2)
+ }
+
+ @Test
+ fun testGetLockForPackage_differentPackages() {
+ val packageName1 = "com.example.app1"
+ val packageName2 = "com.example.app2"
+ val lock1 = serviceImpl.getLockForPackage(packageName1)
+ val lock2 = serviceImpl.getLockForPackage(packageName2)
+
+ // Assert that different lock objects are returned for different package names
+ assertThat(lock1).isNotEqualTo(lock2)
+ }
+
+ @Ignore("Hard to deterministically trigger the garbage collector.")
+ @Test
+ fun testWeakReference_garbageCollected_differentLockAfterGC() = runTest {
+ // Create a large number of temporary objects to put pressure on the GC
+ val tempObjects = MutableList<Any?>(10000000) { Any() }
+ var callingPackage: String? = "com.example.app"
+ var lock1: Any? = serviceImpl.getLockForPackage(callingPackage)
+ callingPackage = null // Set the key to null
+ val lock1Hash = lock1.hashCode()
+ lock1 = null
+
+ // Create memory pressure
+ repeat(3) {
+ for (i in 1..100) {
+ "a".repeat(10000)
+ }
+ System.gc() // Suggest garbage collection
+ System.runFinalization()
+ }
+ // Get the lock again - it should be a different object now
+ val lock2 = serviceImpl.getLockForPackage("com.example.app")
+ // Assert that the lock objects are different
+ assertThat(lock1Hash).isNotEqualTo(lock2.hashCode())
+ }
+}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index bc64e158e830..96fb4535b992 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -34,7 +34,6 @@ import android.util.ArraySet
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.infra.AndroidFuture
-import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.atomic.AtomicBoolean
import org.junit.Test
@@ -392,6 +391,10 @@ class MetadataSyncAdapterTest {
return AndroidFuture.completedFuture(mutableListOf())
}
}
+
+ override fun close() {
+ Log.d("FakeRuntimeMetadataSearchSession", "Closing session")
+ }
}
return AndroidFuture.completedFuture(futureSearchResults)
}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 37a34eeb9724..205ff058275a 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -29,7 +29,6 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_USB" />
- <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 312df4391e2d..0e9dfedac0c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -1118,7 +1118,8 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testAutoBrightnessInDoze_useNormalBrightnessForDozeFalse_scaleScreenOn()
throws Exception {
- when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)).thenReturn(
+ true);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -1154,7 +1155,8 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testAutoBrightnessInDoze_useNormalBrightnessForDozeTrue_notScaleScreenOn()
throws Exception {
- when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)).thenReturn(
+ true);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
index 06f1b27410ff..a8708f9f9e6e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -223,7 +223,8 @@ public class BrightnessSynchronizerTest {
mIntRangeUserPerceptionEnabled);
mSynchronizer.startSynchronizing();
verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
- isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ isA(Handler.class), eq(0L),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
mDisplayListener = mDisplayListenerCaptor.getValue();
verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
index 44c7dec7633e..cd0bd41d9f05 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -130,34 +130,64 @@ public class BrightnessTrackerTest {
}
@Test
- public void testStartStopTrackerScreenOnOff() {
+ public void testStartStopTrackerScreenStates() {
mInjector.mInteractive = false;
+ mInjector.mDisplayState = Display.STATE_OFF;
startTracker(mTracker);
assertNull(mInjector.mSensorListener);
assertNotNull(mInjector.mBroadcastReceiver);
assertTrue(mInjector.mIdleScheduled);
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.sendInteractivityChange(true);
+ mInjector.setDisplayState(Display.STATE_ON);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
+ assertNotNull(mInjector.mDisplayListener);
+
+ mInjector.setDisplayState(Display.STATE_OFF);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+
+ mInjector.setDisplayState(Display.STATE_DOZE);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+
+ mInjector.setDisplayState(Display.STATE_DOZE_SUSPEND);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
- mInjector.sendScreenChange(/* screenOn= */ false);
+ mInjector.setDisplayState(Display.STATE_ON_SUSPEND);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
- // Turn screen on while brightness mode is manual
+ // Screen on while device is not interactive
+ mInjector.setDisplayState(Display.STATE_ON);
+ mInjector.sendInteractivityChange(false);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+ assertNull(mInjector.mDisplayListener);
+
+ // Device becomes interactive while brightness mode is manual
mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.sendInteractivityChange(true);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
+ assertNull(mInjector.mDisplayListener);
// Set brightness mode to automatic while screen is off.
- mInjector.sendScreenChange(/* screenOn= */ false);
mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+ mInjector.setDisplayState(Display.STATE_OFF);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+ assertNotNull(mInjector.mDisplayListener);
+
+ // Set brightness mode to automatic while screen is in doze.
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+ mInjector.setDisplayState(Display.STATE_DOZE);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Turn on screen while brightness mode is automatic.
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.setDisplayState(Display.STATE_ON);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
@@ -166,11 +196,11 @@ public class BrightnessTrackerTest {
assertNull(mInjector.mBroadcastReceiver);
assertFalse(mInjector.mIdleScheduled);
assertFalse(mInjector.mColorSamplingEnabled);
+ assertNull(mInjector.mDisplayListener);
}
@Test
public void testModifyBrightnessConfiguration() {
- mInjector.mInteractive = true;
// Start with tracker not listening for color samples.
startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
assertFalse(mInjector.mColorSamplingEnabled);
@@ -186,13 +216,17 @@ public class BrightnessTrackerTest {
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is off, update config to turn on color sampling.
- mInjector.sendScreenChange(/* screenOn= */ false);
+ mInjector.setDisplayState(Display.STATE_OFF);
mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
mInjector.waitForHandler();
assertFalse(mInjector.mColorSamplingEnabled);
+ // Pretend screen is in doze
+ mInjector.setDisplayState(Display.STATE_DOZE);
+ assertFalse(mInjector.mColorSamplingEnabled);
+
// Pretend screen is on.
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.setDisplayState(Display.STATE_ON);
assertTrue(mInjector.mColorSamplingEnabled);
mTracker.stop();
@@ -208,7 +242,6 @@ public class BrightnessTrackerTest {
mInjector.mDefaultSamplingAttributes.getComponentMask());
startTracker(mTracker);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
}
@Test
@@ -220,7 +253,6 @@ public class BrightnessTrackerTest {
0x2);
startTracker(mTracker);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
}
@Test
@@ -228,14 +260,12 @@ public class BrightnessTrackerTest {
mInjector.mDefaultSamplingAttributes = null;
startTracker(mTracker);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
}
@Test
public void testColorSampling_FrameRateChange() {
startTracker(mTracker);
assertTrue(mInjector.mColorSamplingEnabled);
- assertNotNull(mInjector.mDisplayListener);
int noFramesSampled = mInjector.mNoColorSamplingFrames;
mInjector.mFrameRate = 120.0f;
// Wrong display
@@ -248,7 +278,6 @@ public class BrightnessTrackerTest {
@Test
public void testAdaptiveOnOff() {
- mInjector.mInteractive = true;
mInjector.mIsBrightnessModeAutomatic = false;
startTracker(mTracker);
assertNull(mInjector.mSensorListener);
@@ -256,7 +285,6 @@ public class BrightnessTrackerTest {
assertNotNull(mInjector.mContentObserver);
assertTrue(mInjector.mIdleScheduled);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
assertNotNull(mInjector.mSensorListener);
@@ -835,12 +863,14 @@ public class BrightnessTrackerTest {
mInjector.waitForHandler();
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mDisplayListener);
// Resetting sensor should start listener again
mTracker.setLightSensor(mLightSensorFake);
mInjector.waitForHandler();
assertNotNull(mInjector.mSensorListener);
assertEquals(mInjector.mLightSensor, mLightSensorFake);
+ assertNotNull(mInjector.mDisplayListener);
Sensor secondSensor = new Sensor(mInputSensorInfoMock);
// Setting a different listener should keep things working
@@ -848,6 +878,7 @@ public class BrightnessTrackerTest {
mInjector.waitForHandler();
assertNotNull(mInjector.mSensorListener);
assertEquals(mInjector.mLightSensor, secondSensor);
+ assertNotNull(mInjector.mDisplayListener);
}
@Test
@@ -862,6 +893,7 @@ public class BrightnessTrackerTest {
startTracker(mTracker);
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mDisplayListener);
}
@Test
@@ -895,6 +927,7 @@ public class BrightnessTrackerTest {
assertNull(mInjector.mContentObserver);
assertNull(mInjector.mBroadcastReceiver);
assertFalse(mInjector.mIdleScheduled);
+ assertNull(mInjector.mDisplayListener);
// mInjector asserts that we aren't removing a null receiver
mTracker.stop();
@@ -1017,6 +1050,7 @@ public class BrightnessTrackerTest {
Handler mHandler;
boolean mIdleScheduled;
boolean mInteractive = true;
+ int mDisplayState = Display.STATE_ON;
int[] mProfiles;
ContentObserver mContentObserver;
boolean mIsBrightnessModeAutomatic = true;
@@ -1042,14 +1076,20 @@ public class BrightnessTrackerTest {
waitForHandler();
}
- void sendScreenChange(boolean screenOn) {
- mInteractive = screenOn;
+ void sendInteractivityChange(boolean interactive) {
+ mInteractive = interactive;
Intent intent = new Intent();
- intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+ intent.setAction(interactive ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
waitForHandler();
}
+ void setDisplayState(int state) {
+ mDisplayState = state;
+ mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+ waitForHandler();
+ }
+
void waitForHandler() {
Idle idle = new Idle();
mHandler.getLooper().getQueue().addIdleHandler(idle);
@@ -1183,6 +1223,11 @@ public class BrightnessTrackerTest {
}
@Override
+ public int getDisplayState(Context context) {
+ return mDisplayState;
+ }
+
+ @Override
public int getNightDisplayColorTemperature(Context context) {
return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
mDefaultNightModeColorTemperature);
@@ -1239,7 +1284,7 @@ public class BrightnessTrackerTest {
}
@Override
- public void unRegisterDisplayListener(Context context,
+ public void unregisterDisplayListener(Context context,
DisplayManager.DisplayListener listener) {
mDisplayListener = null;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index fd05b26c320b..2fd135e5f57a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN;
@@ -157,6 +158,7 @@ public final class DisplayDeviceConfigTest {
.getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ assertEquals(0, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -253,6 +255,7 @@ public final class DisplayDeviceConfigTest {
.getLux().intValue());
assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
.getTimeout().intValue());
+ assertEquals(1000, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -263,7 +266,7 @@ public final class DisplayDeviceConfigTest {
mDisplayDeviceConfig.getPowerThrottlingConfigData();
assertNotNull(powerThrottlingConfigData);
assertEquals(0.1f, powerThrottlingConfigData.brightnessLowestCapAllowed, SMALL_DELTA);
- assertEquals(15f, powerThrottlingConfigData.customAnimationRateSec, SMALL_DELTA);
+ assertEquals(15f, powerThrottlingConfigData.customAnimationRate, SMALL_DELTA);
assertEquals(20000, powerThrottlingConfigData.pollingWindowMaxMillis);
assertEquals(10000, powerThrottlingConfigData.pollingWindowMinMillis);
}
@@ -392,7 +395,7 @@ public final class DisplayDeviceConfigTest {
public void testInvalidLuxThrottling() throws Exception {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getInvalidLuxThrottling(), getValidProxSensor(),
- /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
mDisplayDeviceConfig.getLuxThrottlingData();
@@ -600,7 +603,7 @@ public final class DisplayDeviceConfigTest {
public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues(),
- /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
assertNull(mDisplayDeviceConfig.getProximitySensor());
}
@@ -608,7 +611,7 @@ public final class DisplayDeviceConfigTest {
public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(),
- /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
assertEquals("test_proximity_sensor",
mDisplayDeviceConfig.getProximitySensor().type);
assertEquals("Test Proximity Sensor",
@@ -803,7 +806,7 @@ public final class DisplayDeviceConfigTest {
@Test
public void testBrightnessRamps_IdleFallsBackToConfigInteractive() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
@@ -820,14 +823,14 @@ public final class DisplayDeviceConfigTest {
@Test
public void testBrightnessCapForWearBedtimeMode() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
}
@Test
public void testAutoBrightnessBrighteningLevels() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertArrayEquals(new float[]{0.0f, 80},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
@@ -883,6 +886,34 @@ public final class DisplayDeviceConfigTest {
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
AUTO_BRIGHTNESS_MODE_DOZE,
Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
+
+ // Wear Bedtime mode curve
+ assertArrayEquals(new float[]{0.0f, 10.0f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.20f, 0.30f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 20.0f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.30f, 0.65f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 30.0f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.65f, 0.95f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
}
@Test
@@ -890,7 +921,7 @@ public final class DisplayDeviceConfigTest {
when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false);
setupDisplayDeviceConfigFromConfigResourceFile();
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
brightnessIntToFloat(150)},
@@ -929,7 +960,7 @@ public final class DisplayDeviceConfigTest {
when(mFlags.isEvenDimmerEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_evenDimmerEnabled)).thenReturn(true);
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ true));
assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA);
@@ -1294,10 +1325,55 @@ public final class DisplayDeviceConfigTest {
+ "</screenBrightnessRampSlowIncreaseIdle>\n";
}
+ private String getBedTimeModeWearCurveConfig() {
+ return "<luxToBrightnessMapping>\n"
+ + "<mode>bedtime_wear</mode>\n"
+ + "<setting>dim</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.2</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>10</first>\n"
+ + "<second>0.3</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<mode>bedtime_wear</mode>\n"
+ + "<setting>normal</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.3</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>20</first>\n"
+ + "<second>0.65</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<mode>bedtime_wear</mode>\n"
+ + "<setting>bright</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.65</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>30</first>\n"
+ + "<second>0.95</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n";
+ }
+
private String getPowerThrottlingConfig() {
return "<powerThrottlingConfig >\n"
+ "<brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>\n"
- + "<customAnimationRateSec>15</customAnimationRateSec>\n"
+ + "<customAnimationRate>15</customAnimationRate>\n"
+ "<pollingWindowMaxMillis>20000</pollingWindowMaxMillis>\n"
+ "<pollingWindowMinMillis>10000</pollingWindowMinMillis>\n"
+ "<powerThrottlingMap>\n"
@@ -1365,7 +1441,7 @@ public final class DisplayDeviceConfigTest {
private String getContent() {
return getContent(getValidLuxThrottling(), getValidProxSensor(),
- /* includeIdleMode= */ true, false);
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false);
}
private String getContent(String brightnessCapConfig, String proxSensor,
@@ -1479,6 +1555,8 @@ public final class DisplayDeviceConfigTest {
+ "</point>\n"
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ + getBedTimeModeWearCurveConfig()
+ + "<idleStylusTimeoutMillis>1000</idleStylusTimeoutMillis>\n"
+ "</autoBrightness>\n"
+ getPowerThrottlingConfig()
+ "<highBrightnessMode enabled=\"true\">\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index 90f62577b261..d00e2c677930 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -25,6 +25,7 @@ import static android.util.DisplayMetrics.DENSITY_MEDIUM;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.app.Instrumentation;
@@ -38,6 +39,9 @@ import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.platform.test.annotations.AppModeSdkSandbox;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Log;
import android.util.SparseArray;
@@ -46,15 +50,19 @@ import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.server.am.Flags;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
@@ -68,6 +76,10 @@ import java.util.concurrent.TimeUnit;
public class DisplayEventDeliveryTest {
private static final String TAG = "DisplayEventDeliveryTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String NAME = TAG;
private static final int WIDTH = 720;
private static final int HEIGHT = 480;
@@ -149,7 +161,6 @@ public class DisplayEventDeliveryTest {
mExpectations.offer(event);
}
-
/**
* Assert that there isn't any unexpected display event from the test activity
*/
@@ -189,19 +200,9 @@ public class DisplayEventDeliveryTest {
@Parameter(0)
public int mDisplayCount;
- /**
- * True if running the test activity in cached mode
- * False if running it in non-cached mode
- */
- @Parameter(1)
- public boolean mCached;
-
- @Parameters(name = "#{index}: {0} {1}")
+ @Parameters(name = "#{index}: {0}")
public static Iterable<? extends Object> data() {
- return Arrays.asList(new Object[][]{
- {1, false}, {2, false}, {3, false}, {10, false},
- {1, true}, {2, true}, {3, true}, {10, true}
- });
+ return Arrays.asList(new Object[][]{ {1}, {2}, {3}, {10} });
}
private class TestHandler extends Handler {
@@ -289,20 +290,51 @@ public class DisplayEventDeliveryTest {
}
/**
- * Create virtual displays, change their configurations and release them
- * mDisplays: the amount of virtual displays to be created
- * mCached: true to run the test activity in cached mode; false in non-cached mode
+ * Return true if the freezer is enabled on this platform.
*/
- @Test
- public void testDisplayEvents() {
- Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + mCached);
+ private boolean isAppFreezerEnabled() {
+ try {
+ return mActivityManager.getService().isAppFreezerEnabled();
+ } catch (Exception e) {
+ Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
+ return false;
+ }
+ }
+
+ private void waitForProcessFreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ private void waitForProcessUnfreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> !mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The number of
+ * displays is set by the {@link #mDisplays} variable.
+ */
+ private void testDisplayEventsInternal(boolean cached, boolean frozen) {
+ Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen);
// Launch DisplayEventActivity and start listening to display events
- launchTestActivity();
+ int pid = launchTestActivity();
- if (mCached) {
- // The test activity in cached mode won't receive the pending display events
+ // The test activity in cached or frozen mode won't receive the pending display events.
+ if (cached) {
makeTestActivityCached();
}
+ if (frozen) {
+ makeTestActivityFrozen(pid);
+ }
// Create new virtual displays
for (int i = 0; i < mDisplayCount; i++) {
@@ -315,8 +347,8 @@ public class DisplayEventDeliveryTest {
}
for (int i = 0; i < mDisplayCount; i++) {
- if (mCached) {
- // DISPLAY_ADDED should be deferred for cached process
+ if (cached || frozen) {
+ // DISPLAY_ADDED should be deferred for a cached or frozen process.
displayBundleAt(i).assertNoDisplayEvents();
} else {
// DISPLAY_ADDED should arrive immediately for non-cached process
@@ -331,8 +363,8 @@ public class DisplayEventDeliveryTest {
}
for (int i = 0; i < mDisplayCount; i++) {
- if (mCached) {
- // DISPLAY_CHANGED should be deferred for cached process
+ if (cached || frozen) {
+ // DISPLAY_CHANGED should be deferred for cached or frozen process.
displayBundleAt(i).assertNoDisplayEvents();
} else {
// DISPLAY_CHANGED should arrive immediately for non-cached process
@@ -340,10 +372,16 @@ public class DisplayEventDeliveryTest {
}
}
- if (mCached) {
- // The test activity becomes non-cached and should receive the pending display events
+ // Unfreeze the test activity, if it was frozen.
+ if (frozen) {
+ makeTestActivityUnfrozen(pid);
+ }
+
+ if (cached || frozen) {
+ // Always ensure the test activity is not cached.
bringTestActivityTop();
+ // The test activity becomes non-cached and should receive the pending display events
for (int i = 0; i < mDisplayCount; i++) {
// The pending DISPLAY_ADDED & DISPLAY_CHANGED should arrive now
displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED);
@@ -363,9 +401,48 @@ public class DisplayEventDeliveryTest {
}
/**
- * Launch the test activity that would listen to display events
+ * Create virtual displays, change their configurations and release them.
+ */
+ @Test
+ public void testDisplayEvents() {
+ testDisplayEventsInternal(false, false);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * moved to cached and the test verifies that no events are delivered to the cached app.
*/
- private void launchTestActivity() {
+ @Test
+ public void testDisplayEventsCached() {
+ testDisplayEventsInternal(true, false);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * frozen and the test verifies that no events are delivered to the frozen app.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN)
+ @Test
+ public void testDisplayEventsFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testDisplayEventsInternal(false, true);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * cached and frozen and the test verifies that no events are delivered to the app.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN)
+ @Test
+ public void testDisplayEventsCachedFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testDisplayEventsInternal(true, true);
+ }
+
+ /**
+ * Launch the test activity that would listen to display events. Return its process ID.
+ */
+ private int launchTestActivity() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
intent.putExtra(TEST_MESSENGER, mMessenger);
@@ -377,6 +454,18 @@ public class DisplayEventDeliveryTest {
},
android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
waitLatch(mLatchActivityLaunch);
+
+ try {
+ String cmd = "pidof " + TEST_PACKAGE;
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
+ return Integer.parseInt(result.trim());
+ } catch (IOException e) {
+ fail("failed to get pid of test package");
+ return 0;
+ } catch (NumberFormatException e) {
+ fail("failed to parse pid " + e);
+ return 0;
+ }
}
/**
@@ -415,6 +504,45 @@ public class DisplayEventDeliveryTest {
waitLatch(mLatchActivityCached);
}
+ // Sleep, ignoring interrupts.
+ private void pause(int s) {
+ try { Thread.sleep(s * 1000); } catch (Exception e) { }
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ private void makeTestActivityFrozen(int pid) {
+ // The delay here is meant to allow pending binder transactions to drain. A process
+ // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
+ // process more than a few times will result in the system killing the process.
+ pause(5);
+ try {
+ String cmd = "am freeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessFreeze(pid, 5 * 1000);
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ private void makeTestActivityUnfrozen(int pid) {
+ try {
+ String cmd = "am unfreeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessUnfreeze(pid, 5 * 1000);
+ }
+
/**
* Create a virtual display
*
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 255dcb083518..365cbaed2aac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -17,14 +17,19 @@
package com.android.server.display;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_MIRROR_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
import static android.Manifest.permission.MANAGE_DISPLAYS;
+import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -37,6 +42,7 @@ import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.c
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -49,6 +55,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
@@ -93,6 +100,7 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -108,15 +116,18 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
+import android.os.test.FakePermissionEnforcer;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.test.mock.MockContentResolver;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.Spline;
import android.view.ContentRecordingSession;
@@ -135,11 +146,14 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.am.BatteryStatsService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayManagerService.DeviceStateListener;
import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -150,6 +164,7 @@ import com.android.server.display.layout.Layout;
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.input.InputManagerInternal;
import com.android.server.lights.LightsManager;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -195,11 +210,13 @@ public class DisplayManagerServiceTest {
private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
- private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ private static final long STANDARD_DISPLAY_EVENTS =
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
- STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+ STANDARD_DISPLAY_EVENTS
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED";
private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED";
@@ -210,9 +227,13 @@ public class DisplayManagerServiceTest {
"EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED";
private static final String EVENT_DISPLAY_CONNECTED = "EVENT_DISPLAY_CONNECTED";
private static final String EVENT_DISPLAY_DISCONNECTED = "EVENT_DISPLAY_DISCONNECTED";
+ private static final String EVENT_DISPLAY_REFRESH_RATE_CHANGED =
+ "EVENT_DISPLAY_REFRESH_RATE_CHANGED";
+ private static final String EVENT_DISPLAY_STATE_CHANGED = "EVENT_DISPLAY_STATE_CHANGED";
private static final String DISPLAY_GROUP_EVENT_ADDED = "DISPLAY_GROUP_EVENT_ADDED";
private static final String DISPLAY_GROUP_EVENT_REMOVED = "DISPLAY_GROUP_EVENT_REMOVED";
private static final String DISPLAY_GROUP_EVENT_CHANGED = "DISPLAY_GROUP_EVENT_CHANGED";
+ private static final String TOPOLOGY_CHANGED_EVENT = "TOPOLOGY_CHANGED_EVENT";
@Rule(order = 0)
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -241,6 +262,8 @@ public class DisplayManagerServiceTest {
private int[] mAllowedHdrOutputTypes;
+ private final FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer();
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -373,6 +396,10 @@ public class DisplayManagerServiceTest {
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@Mock DisplayManagerFlags mMockFlags;
+ @Mock WindowManagerPolicy mMockedWindowManagerPolicy;
+
+ @Mock IBatteryStats mMockedBatteryStats;
+
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule =
new ExtendedMockitoRule.Builder(this)
@@ -414,6 +441,13 @@ public class DisplayManagerServiceTest {
when(mContext.getResources()).thenReturn(mResources);
mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
+ mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
+ mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE);
+ doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+ eq(PermissionEnforcer.class));
+ doReturn(mPermissionEnforcer).when(mContext).getSystemService(
+ eq(Context.PERMISSION_ENFORCER_SERVICE));
+
VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
@@ -1066,9 +1100,9 @@ public class DisplayManagerServiceTest {
firstDisplayId);
}
- /** Tests that the virtual device is created in a device display group. */
+ /** Tests that a trusted virtual display is created in a device display group. */
@Test
- public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
+ public void createVirtualDisplay_addsTrustedDisplaysToDeviceDisplayGroups() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -1079,12 +1113,16 @@ public class DisplayManagerServiceTest {
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
// Create a first virtual display. A display group should be created for this display on the
// virtual device.
final VirtualDisplayConfig.Builder builder1 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group 1");
-
+ .setUniqueId("uniqueId --- device display group")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED);
int displayId1 =
localService.createVirtualDisplay(
builder1.build(),
@@ -1095,12 +1133,14 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+ assertNotEquals(displayGroupId1, Display.DEFAULT_DISPLAY_GROUP);
// Create a second virtual display. This should be added to the previously created display
// group.
final VirtualDisplayConfig.Builder builder2 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group 1");
+ .setUniqueId("uniqueId --- device display group")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED);
int displayId2 =
localService.createVirtualDisplay(
@@ -1119,6 +1159,36 @@ public class DisplayManagerServiceTest {
displayGroupId2);
}
+ /** Tests that an untrusted virtual display is created in the default display group. */
+ @Test
+ public void createVirtualDisplay_addsUntrustedDisplayToDefaultDisplayGroups() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ // Create the virtual display. It is untrusted, so it should go into the default group.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group");
+
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
+ int displayGroupId = localService.getDisplayInfo(displayId).displayGroupId;
+ assertEquals(displayGroupId, Display.DEFAULT_DISPLAY_GROUP);
+ }
+
/**
* Tests that the virtual display is not added to the device display group when
* VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
@@ -1136,11 +1206,15 @@ public class DisplayManagerServiceTest {
when(virtualDevice.getDeviceId()).thenReturn(1);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
// Create a first virtual display. A display group should be created for this display on the
// virtual device.
final VirtualDisplayConfig.Builder builder1 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group");
+ .setUniqueId("uniqueId --- device display group")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED);
int displayId1 =
localService.createVirtualDisplay(
@@ -1152,12 +1226,14 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+ assertNotEquals(displayGroupId1, Display.DEFAULT_DISPLAY_GROUP);
// Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
// the display should not be added to the previously created display group.
final VirtualDisplayConfig.Builder builder2 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
+ | VIRTUAL_DISPLAY_FLAG_TRUSTED)
.setUniqueId("uniqueId --- own display group");
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
@@ -1172,6 +1248,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+ assertNotEquals(displayGroupId2, Display.DEFAULT_DISPLAY_GROUP);
assertNotEquals(
"Display 1 should be in the device display group and display 2 in its own display"
@@ -1206,7 +1283,8 @@ public class DisplayManagerServiceTest {
final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
.setUniqueId("uniqueId --- device display group 1")
- .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+ | VIRTUAL_DISPLAY_FLAG_TRUSTED)
.build();
int deviceDisplayGroupDisplayId =
@@ -1233,6 +1311,7 @@ public class DisplayManagerServiceTest {
.setUniqueId("uniqueId --- own display group 1")
.setFlags(
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+ | VIRTUAL_DISPLAY_FLAG_TRUSTED
| VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
.build();
@@ -1308,6 +1387,42 @@ public class DisplayManagerServiceTest {
}
/**
+ * Tests that it is not allowed to create an auto-mirror virtual display for a virtual device
+ * without ADD_MIRROR_DISPLAY permission / without the mirror display capability.
+ */
+ @Test
+ public void createAutoMirrorDisplay_withoutPermission_throwsException() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+ }
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ assertThrows(SecurityException.class, () -> {
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ });
+ }
+
+ /**
* Tests that the virtual display is added to the default display group when created with
* VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
*/
@@ -1319,6 +1434,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1351,6 +1472,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1417,6 +1544,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1452,6 +1585,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1524,6 +1663,111 @@ public class DisplayManagerServiceTest {
}
@Test
+ public void testGetDisplayIdsForGroup() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ // Create display 1
+ FakeDisplayDevice displayDevice1 =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ LogicalDisplay display1 = logicalDisplayMapper.getDisplayLocked(displayDevice1);
+ final int groupId1 = display1.getDisplayInfoLocked().displayGroupId;
+ // Create display 2
+ FakeDisplayDevice displayDevice2 =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ LogicalDisplay display2 = logicalDisplayMapper.getDisplayLocked(displayDevice2);
+ final int groupId2 = display2.getDisplayInfoLocked().displayGroupId;
+ // Both displays should be in the same display group
+ assertEquals(groupId1, groupId2);
+ final int[] expectedDisplayIds = new int[]{
+ display1.getDisplayIdLocked(), display2.getDisplayIdLocked()};
+
+ final int[] displayIds = localService.getDisplayIdsForGroup(groupId1);
+
+ assertArrayEquals(expectedDisplayIds, displayIds);
+ }
+
+ @Test
+ public void testGetDisplayIdsForUnknownGroup() throws Exception {
+ final int unknownDisplayGroupId = 999;
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ // Verify that display manager does not have display group
+ assertNull(logicalDisplayMapper.getDisplayGroupLocked(unknownDisplayGroupId));
+
+ final int[] displayIds = localService.getDisplayIdsForGroup(unknownDisplayGroupId);
+
+ assertEquals(0, displayIds.length);
+ }
+
+ @Test
+ public void testGetDisplayIdsByGroupsIds() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ // Create display 1
+ FakeDisplayDevice displayDevice1 =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ LogicalDisplay display1 = logicalDisplayMapper.getDisplayLocked(displayDevice1);
+ final int groupId1 = display1.getDisplayInfoLocked().displayGroupId;
+ // Create display 2
+ FakeDisplayDevice displayDevice2 =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ LogicalDisplay display2 = logicalDisplayMapper.getDisplayLocked(displayDevice2);
+ final int groupId2 = display2.getDisplayInfoLocked().displayGroupId;
+ // Both displays should be in the same display group
+ assertEquals(groupId1, groupId2);
+ final int[] displayIds = new int[]{
+ display1.getDisplayIdLocked(), display2.getDisplayIdLocked()};
+ final SparseArray<int[]> expectedDisplayGroups = new SparseArray<>();
+ expectedDisplayGroups.put(groupId1, displayIds);
+
+ final SparseArray<int[]> displayGroups = localService.getDisplayIdsByGroupsIds();
+
+ for (int i = 0; i < expectedDisplayGroups.size(); i++) {
+ final int groupId = expectedDisplayGroups.keyAt(i);
+ assertTrue(displayGroups.contains(groupId));
+ assertArrayEquals(expectedDisplayGroups.get(groupId), displayGroups.get(groupId));
+ }
+ }
+
+ @Test
+ public void testGetDisplayIdsByGroupsIds_multipleDisplayGroups() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ // Create display 1
+ FakeDisplayDevice displayDevice1 =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ LogicalDisplay display1 = logicalDisplayMapper.getDisplayLocked(displayDevice1);
+ final int groupId1 = display1.getDisplayInfoLocked().displayGroupId;
+ // Create display 2
+ FakeDisplayDevice displayDevice2 =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ LogicalDisplay display2 = logicalDisplayMapper.getDisplayLocked(displayDevice2);
+ final int groupId2 = display2.getDisplayInfoLocked().displayGroupId;
+ // Both displays should be in different display groups
+ assertNotEquals(groupId1, groupId2);
+ final SparseArray<int[]> expectedDisplayGroups = new SparseArray<>();
+ expectedDisplayGroups.put(groupId1, new int[]{display1.getDisplayIdLocked()});
+ expectedDisplayGroups.put(groupId2, new int[]{display2.getDisplayIdLocked()});
+
+ final SparseArray<int[]> displayGroups = localService.getDisplayIdsByGroupsIds();
+
+ assertEquals(expectedDisplayGroups.size(), displayGroups.size());
+ for (int i = 0; i < expectedDisplayGroups.size(); i++) {
+ final int groupId = expectedDisplayGroups.keyAt(i);
+ assertTrue(displayGroups.contains(groupId));
+ assertArrayEquals(expectedDisplayGroups.get(groupId), displayGroups.get(groupId));
+ }
+ }
+
+ @Test
public void testCreateVirtualDisplay_isValidProjection_notValid()
throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
@@ -1773,7 +2017,7 @@ public class DisplayManagerServiceTest {
/**
* Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
- * ADD_TRUSTED_DISPLAY is granted.
+ * ADD_TRUSTED_DISPLAY is granted and that display is not in the default display group.
*/
@Test
public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
@@ -1802,6 +2046,9 @@ public class DisplayManagerServiceTest {
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+ int displayGroupId = bs.getDisplayInfo(displayId).displayGroupId;
+ assertNotEquals(displayGroupId, Display.DEFAULT_DISPLAY_GROUP);
}
/**
@@ -1836,11 +2083,11 @@ public class DisplayManagerServiceTest {
}
/**
- * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
- * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
+ * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is not allowed when called with
+ * a virtual device, if ADD_TRUSTED_DISPLAY is not granted.
*/
@Test
- public void testOwnDisplayGroup_allowCreationWithVirtualDevice() throws Exception {
+ public void testOwnDisplayGroup_disallowCreationWithVirtualDevice() throws Exception {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -1861,16 +2108,16 @@ public class DisplayManagerServiceTest {
when(virtualDevice.getDeviceId()).thenReturn(1);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
- int displayId = localService.createVirtualDisplay(builder.build(),
- mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
- mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
- verify(mMockProjectionService, never()).setContentRecordingSession(any(),
- nullable(IMediaProjection.class));
- performTraversalInternal(displayManager);
- displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
- DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
- assertNotNull(ddi);
- assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ try {
+ localService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+ fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
+ + "ADD_TRUSTED_DISPLAY permission should throw SecurityException even if "
+ + "called with a virtual device.");
+ } catch (SecurityException e) {
+ // SecurityException is expected
+ }
}
/**
@@ -2178,7 +2425,7 @@ public class DisplayManagerServiceTest {
// register display listener callback
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
- & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
+ & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED;
displayManagerBinderService.registerCallbackWithEventMask(callback,
allEventsExceptDisplayAdded);
@@ -2249,7 +2496,7 @@ public class DisplayManagerServiceTest {
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
- & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
displayManagerBinderService.registerCallbackWithEventMask(callback,
allEventsExceptDisplayRemoved);
@@ -2632,6 +2879,78 @@ public class DisplayManagerServiceTest {
}
@Test
+ public void testConnectExternalDisplay_withDisplayManagement_allowsEnableAndDisableDisplay() {
+ when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ when(mMockFlags.isApplyDisplayChangedDuringDisplayAddedEnabled()).thenReturn(true);
+ manageDisplaysPermission(/* granted= */ true);
+ LocalServices.addService(WindowManagerPolicy.class, mMockedWindowManagerPolicy);
+ BatteryStatsService.overrideService(mMockedBatteryStats);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ localService.registerDisplayGroupListener(callback);
+
+ // Create default display device
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
+ FakeDisplayDevice defaultDisplayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ callback.waitForExpectedEvent();
+ LogicalDisplay defaultDisplay =
+ logicalDisplayMapper.getDisplayLocked(defaultDisplayDevice, false);
+ callback.clear();
+
+ // Create external display device
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, new float[]{60f},
+ Display.TYPE_EXTERNAL, callback);
+ callback.waitForExpectedEvent();
+ LogicalDisplay display =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+ assertThat(display.isEnabledLocked()).isTrue();
+ callback.clear();
+
+ callback.expectsEvent(FakeDisplayDevice.COMMITTED_DISPLAY_STATE_CHANGED);
+ initDisplayPowerController(localService);
+ // Initial power request, should have happened from PowerManagerService.
+ localService.requestPowerState(defaultDisplay.getDisplayInfoLocked().displayGroupId,
+ new DisplayManagerInternal.DisplayPowerRequest(),
+ /*waitForNegativeProximity=*/ false);
+ localService.requestPowerState(display.getDisplayInfoLocked().displayGroupId,
+ new DisplayManagerInternal.DisplayPowerRequest(),
+ /*waitForNegativeProximity=*/ false);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ callback.waitForExpectedEvent();
+
+ assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState)
+ .isEqualTo(Display.STATE_OFF);
+ assertThat(display.isEnabledLocked()).isFalse();
+ assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CONNECTED,
+ EVENT_DISPLAY_REMOVED).inOrder();
+
+ int displayId = display.getDisplayIdLocked();
+ boolean enabled = display.isEnabledLocked();
+ assertThat(enabled).isFalse();
+
+ for (int i = 0; i < 9; i++) {
+ callback.expectsEvent(FakeDisplayDevice.COMMITTED_DISPLAY_STATE_CHANGED);
+ enabled = !enabled;
+ Slog.d("DisplayManagerServiceTest", "enabled=" + enabled);
+ displayManager.enableConnectedDisplay(displayId, enabled);
+ callback.waitForExpectedEvent();
+ assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState)
+ .isEqualTo(enabled ? Display.STATE_ON : Display.STATE_OFF);
+ assertThat(defaultDisplayDevice.getDisplayDeviceInfoLocked().committedState)
+ .isEqualTo(Display.STATE_ON);
+ }
+ callback.expectsEvent(FakeDisplayDevice.COMMITTED_DISPLAY_STATE_CHANGED);
+ callback.waitForNonExpectedEvent();
+ }
+
+ @Test
public void testConnectInternalDisplay_withDisplayManagement_shouldConnectAndAddDisplay() {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
@@ -2667,7 +2986,7 @@ public class DisplayManagerServiceTest {
displayManager.setDisplayState(display.getDisplayIdLocked(), Display.STATE_ON);
assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState)
- .isEqualTo(Display.STATE_ON);
+ .isEqualTo(Display.STATE_UNKNOWN);
assertThat(displayManager.requestDisplayPower(display.getDisplayIdLocked(),
Display.STATE_OFF)).isTrue();
@@ -2700,7 +3019,7 @@ public class DisplayManagerServiceTest {
var displayId = display.getDisplayIdLocked();
assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState)
- .isEqualTo(Display.STATE_ON);
+ .isEqualTo(Display.STATE_UNKNOWN);
assertThrows(SecurityException.class,
() -> bs.requestDisplayPower(displayId, Display.STATE_UNKNOWN));
@@ -3367,6 +3686,150 @@ public class DisplayManagerServiceTest {
verify(dpc).onDisplayChanged(/* hbmMetadata= */ null, Layout.NO_LEAD_DISPLAY);
}
+ @Test
+ public void testCreateAndReleaseVirtualDisplay_CalledWithTheSameUid() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ registerDefaultDisplays(displayManager);
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+ VirtualDisplayConfig config = mock(VirtualDisplayConfig.class);
+ Surface surface = mock(Surface.class);
+ when(config.getSurface()).thenReturn(surface);
+ int callingUid = Binder.getCallingUid();
+ IBinder binder = mock(IBinder.class);
+ when(mMockAppToken.asBinder()).thenReturn(binder);
+ String uniqueId = "123";
+ when(config.getUniqueId()).thenReturn(uniqueId);
+
+ bs.createVirtualDisplay(config, mMockAppToken, /* projection= */ null, PACKAGE_NAME);
+ verify(mMockVirtualDisplayAdapter).createVirtualDisplayLocked(eq(mMockAppToken),
+ /* projection= */ isNull(), eq(callingUid), eq(PACKAGE_NAME),
+ eq("virtual:" + PACKAGE_NAME + ":" + uniqueId), eq(surface), /* flags= */ anyInt(),
+ eq(config));
+
+ bs.releaseVirtualDisplay(mMockAppToken);
+ verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid);
+ }
+
+ @Test
+ public void testGetDisplayTopology() {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ DisplayTopology topology = displayManagerBinderService.getDisplayTopology();
+ assertNotNull(topology);
+ DisplayTopology.TreeNode display = topology.getRoot();
+ assertNotNull(display);
+ assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId());
+ }
+
+ @Test
+ public void testGetDisplayTopology_NullIfFlagDisabled() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ DisplayTopology topology = displayManagerBinderService.getDisplayTopology();
+ assertNull(topology);
+ }
+
+ @Test
+ public void testGetDisplayTopology_withoutPermission_shouldThrowException() {
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ assertThrows(SecurityException.class, displayManagerBinderService::getDisplayTopology);
+ }
+
+ @Test
+ public void testSetDisplayTopology() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ }
+
+ @Test
+ public void testSetDisplayTopology_withoutPermission_shouldThrowException() {
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ assertThrows(SecurityException.class,
+ () -> displayManagerBinderService.setDisplayTopology(new DisplayTopology()));
+ }
+
+ @Test
+ public void testShouldNotifyTopologyChanged() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(callback,
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
+ waitForIdleHandler(handler);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).containsExactly(TOPOLOGY_CHANGED_EVENT);
+ }
+
+ @Test
+ public void testShouldNotNotifyTopologyChanged_WhenClientIsNotSubscribed() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ // Only subscribe to display events, not topology events
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(callback,
+ STANDARD_DISPLAY_EVENTS);
+ waitForIdleHandler(handler);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).isEmpty();
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -3550,6 +4013,10 @@ public class DisplayManagerServiceTest {
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
displayDeviceInfo.modeId = modeId;
+ if (modeId > 0 && modeId <= displayDeviceInfo.supportedModes.length) {
+ displayDeviceInfo.renderFrameRate =
+ displayDeviceInfo.supportedModes[modeId - 1].getRefreshRate();
+ }
updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
}
@@ -3633,7 +4100,16 @@ public class DisplayManagerServiceTest {
float[] refreshRates,
float[] vsyncRates,
int displayType) {
+ return createFakeDisplayDevice(displayManager, refreshRates, vsyncRates, displayType, null);
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ float[] refreshRates,
+ float[] vsyncRates,
+ int displayType,
+ FakeDisplayManagerCallback callback) {
FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+ displayDevice.setCallback(callback);
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
int width = 100;
int height = 200;
@@ -3643,6 +4119,7 @@ public class DisplayManagerServiceTest {
new Display.Mode(i + 1, width, height, refreshRates[i], vsyncRates[i],
new float[0], new int[0]);
}
+ displayDeviceInfo.name = "" + displayType;
displayDeviceInfo.modeId = 1;
displayDeviceInfo.type = displayType;
displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
@@ -3707,9 +4184,11 @@ public class DisplayManagerServiceTest {
private void manageDisplaysPermission(boolean granted) {
if (granted) {
doNothing().when(mContext).enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+ mPermissionEnforcer.grant(MANAGE_DISPLAYS);
} else {
doThrow(new SecurityException("MANAGE_DISPLAYS permission denied")).when(mContext)
.enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+ mPermissionEnforcer.revoke(MANAGE_DISPLAYS);
}
}
@@ -3750,6 +4229,19 @@ public class DisplayManagerServiceTest {
}
}
+ void waitForNonExpectedEvent() {
+ waitForNonExpectedEvent(Duration.ofSeconds(1));
+ }
+
+ void waitForNonExpectedEvent(Duration timeout) {
+ try {
+ assertWithMessage("Non Expected '" + mExpectedEvent + "'")
+ .that(mLatch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)).isFalse();
+ } catch (InterruptedException ex) {
+ throw new AssertionError("Waiting for expected event interrupted", ex);
+ }
+ }
+
private void eventSeen(String event) {
if (event.equals(mExpectedEvent)) {
mLatch.countDown();
@@ -3789,6 +4281,12 @@ public class DisplayManagerServiceTest {
eventSeen(DISPLAY_GROUP_EVENT_CHANGED);
}
+ @Override
+ public void onTopologyChanged(DisplayTopology topology) {
+ mReceivedEvents.add(TOPOLOGY_CHANGED_EVENT);
+ eventSeen(TOPOLOGY_CHANGED_EVENT);
+ }
+
public void clear() {
mReceivedEvents.clear();
}
@@ -3809,6 +4307,10 @@ public class DisplayManagerServiceTest {
return EVENT_DISPLAY_CONNECTED;
case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
return EVENT_DISPLAY_DISCONNECTED;
+ case DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED:
+ return EVENT_DISPLAY_REFRESH_RATE_CHANGED;
+ case DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED:
+ return EVENT_DISPLAY_STATE_CHANGED;
default:
return "UNKNOWN: " + eventType;
}
@@ -3820,8 +4322,10 @@ public class DisplayManagerServiceTest {
}
private class FakeDisplayDevice extends DisplayDevice {
+ static final String COMMITTED_DISPLAY_STATE_CHANGED = "requestDisplayStateLocked";
private DisplayDeviceInfo mDisplayDeviceInfo;
private Display.Mode mPreferredMode = new Display.Mode.Builder().build();
+ private FakeDisplayManagerCallback mCallback;
FakeDisplayDevice() {
super(mMockDisplayAdapter, /* displayToken= */ null, /* uniqueId= */ "", mContext);
@@ -3829,7 +4333,7 @@ public class DisplayManagerServiceTest {
public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
mDisplayDeviceInfo = displayDeviceInfo;
- mDisplayDeviceInfo.committedState = Display.STATE_ON;
+ mDisplayDeviceInfo.committedState = Display.STATE_UNKNOWN;
}
@Override
@@ -3866,7 +4370,23 @@ public class DisplayManagerServiceTest {
final float brightnessState,
final float sdrBrightnessState,
@Nullable DisplayOffloadSessionImpl displayOffloadSession) {
- return () -> mDisplayDeviceInfo.committedState = state;
+ return () -> {
+ Slog.d("FakeDisplayDevice", mDisplayDeviceInfo.name
+ + " new state=" + state);
+ if (state != mDisplayDeviceInfo.committedState) {
+ Slog.d("FakeDisplayDevice", mDisplayDeviceInfo.name
+ + " mDisplayDeviceInfo.committedState="
+ + mDisplayDeviceInfo.committedState + " set to " + state);
+ mDisplayDeviceInfo.committedState = state;
+ if (mCallback != null) {
+ mCallback.eventSeen(COMMITTED_DISPLAY_STATE_CHANGED);
+ }
+ }
+ };
+ }
+
+ void setCallback(FakeDisplayManagerCallback callback) {
+ this.mCallback = callback;
}
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index bf5a692ef8ca..8ca39194de3b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
@@ -35,6 +36,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -119,8 +121,8 @@ public final class DisplayPowerControllerTest {
private static final float PROX_SENSOR_MAX_RANGE = 5;
private static final float DOZE_SCALE_FACTOR = 0.34f;
private static final float DEFAULT_DOZE_BRIGHTNESS = 0.121f;
+ private static final float OVERRIDE_BRIGHTNESS = 0.567f;
- private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
@@ -137,6 +139,7 @@ public final class DisplayPowerControllerTest {
private TestLooper mTestLooper;
private Handler mHandler;
private DisplayPowerControllerHolder mHolder;
+ private DisplayBrightnessState mDisplayBrightnessState;
private Sensor mProxSensor;
@Mock
@@ -185,6 +188,7 @@ public final class DisplayPowerControllerTest {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
mHandler = new Handler(mTestLooper.getLooper());
+ mDisplayBrightnessState = DisplayBrightnessState.builder().build();
// Set some settings to minimize unexpected events and have a consistent starting state
Settings.System.putInt(mContext.getContentResolver(),
@@ -1289,7 +1293,6 @@ public final class DisplayPowerControllerTest {
/* ambientLightHorizonLong= */ anyInt(),
eq(lux),
eq(nits),
- any(BrightnessClamperController.class),
any(DisplayManagerFlags.class)
);
}
@@ -1452,10 +1455,11 @@ public final class DisplayPowerControllerTest {
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
- invocation -> DisplayBrightnessState.builder()
- .setIsSlowChange(invocation.getArgument(2))
- .setBrightness(invocation.getArgument(1))
+ when(mHolder.clamperController.clamp(any(), any(), anyFloat(),
+ anyBoolean(), anyInt())).thenAnswer(
+ invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState)
+ .setIsSlowChange(invocation.getArgument(3))
+ .setBrightness(invocation.getArgument(2))
.setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
.setCustomAnimationRate(transitionRate).build());
@@ -1476,10 +1480,11 @@ public final class DisplayPowerControllerTest {
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
- when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
- invocation -> DisplayBrightnessState.builder()
- .setIsSlowChange(invocation.getArgument(2))
- .setBrightness(invocation.getArgument(1))
+ when(mHolder.clamperController.clamp(any(), any(), anyFloat(),
+ anyBoolean(), anyInt())).thenAnswer(
+ invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState)
+ .setIsSlowChange(invocation.getArgument(3))
+ .setBrightness(invocation.getArgument(2))
.setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
.setCustomAnimationRate(transitionRate).build());
@@ -1928,6 +1933,60 @@ public final class DisplayPowerControllerTest {
}
@Test
+ public void testSwitchToBedtimeAutoBrightnessMode_wearBedtimeEnabledAndBrightRequest() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isAutoBrightnessModeBedtimeWearEnabled()).thenReturn(true);
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
+ /* value= */ 1);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController)
+ .switchMode(eq(AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR), /* sendUpdate= */ anyBoolean());
+ }
+
+ @Test
+ public void testNotSwitchToBedtimeAutoBrightnessMode_wearBedtimeDisabled() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isAutoBrightnessModeBedtimeWearEnabled()).thenReturn(true);
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
+ /* value= */ 0);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController)
+ .switchMode(eq(AUTO_BRIGHTNESS_MODE_DEFAULT), /* sendUpdate= */ anyBoolean());
+ }
+
+ @Test
+ public void testSwitchToDozeAutoBrightnessMode_wearBedtimeEnabledAndDozeRequest() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isAutoBrightnessModeBedtimeWearEnabled()).thenReturn(true);
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
+ /* value= */ 1);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController)
+ .switchMode(eq(AUTO_BRIGHTNESS_MODE_DOZE), /* sendUpdate= */ anyBoolean());
+ }
+
+ @Test
public void testOnSwitchUserUpdatesBrightness() {
int userSerial = 12345;
float brightness = 0.65f;
@@ -2060,7 +2119,8 @@ public final class DisplayPowerControllerTest {
@Test
public void testManualBrightness_stateOnPolicyDozeUseNormalBrightnessForDozeFalse_brightnessDoze() {
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled(
+ mContext)).thenReturn(true);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -2095,7 +2155,8 @@ public final class DisplayPowerControllerTest {
@Test
public void testManualBrightness_stateOnPolicyDozeUseNormalBrightnessForDozeTrue_brightnessNormal() {
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled(
+ mContext)).thenReturn(true);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -2127,6 +2188,49 @@ public final class DisplayPowerControllerTest {
}
@Test
+ public void testManualBrightness_stateDozePolicyOnUseNormalBrightnessForDozeTrue_brightnessDoze() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled(
+ mContext)).thenReturn(true);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ float brightness = 0.277f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ when(mHolder.hbmController.getCurrentBrightnessMax())
+ .thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Start with state=DOZE.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ DisplayPowerRequest dprInit = new DisplayPowerRequest();
+ dprInit.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dprInit, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState; initialize to DOZE
+ // Go to state=ON. But state change would be blocked. so, state=DOZE.
+ when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.dozeScreenState = Display.STATE_ON;
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useNormalBrightnessForDoze = true;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState; process turning on.
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ // When state=DOZE, force doze brightness regardless the requested policy.
+ verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
+ /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+ /* ignoreAnimationLimits= */ anyBoolean());
+ }
+
+ @Test
public void testDozeManualBrightness_AbcIsNull() {
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true,
@@ -2181,6 +2285,8 @@ public final class DisplayPowerControllerTest {
verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
/* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
eq(false));
+ // This brightness shouldn't be stored in the setting
+ verify(mHolder.brightnessSetting, never()).setBrightness(DEFAULT_DOZE_BRIGHTNESS);
// The display device changes and the default doze brightness changes
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
@@ -2236,29 +2342,44 @@ public final class DisplayPowerControllerTest {
throws Exception {
when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(false);
- verifyNoteScreenState(Display.DEFAULT_DISPLAY, /* expectNote= */ true);
+ verifyNoteScreenState(
+ Display.DEFAULT_DISPLAY, Display.TYPE_INTERNAL, /* expectNote= */ true);
}
@Test
public void testBatteryStatNotes_enabledOnDefaultDisplayWhenEnabledOnOthers() throws Exception {
when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(true);
- verifyNoteScreenState(Display.DEFAULT_DISPLAY, /* expectNote= */ true);
+ verifyNoteScreenState(
+ Display.DEFAULT_DISPLAY, Display.TYPE_INTERNAL, /* expectNote= */ true);
}
@Test
- public void testBatteryStatNotes_flagGuardedOnNonDefaultDisplays() throws Exception {
+ public void testBatteryStatNotes_flagOff_disabledForNonDefaultDisplays() throws Exception {
when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(false);
- verifyNoteScreenState(/* displayId= */ 2, /* expectNote= */ false);
+ verifyNoteScreenState(/* displayId= */ 2, Display.TYPE_INTERNAL, /* expectNote= */ false);
+ }
+ @Test
+ public void testBatteryStatNotes_enabledOnlyOnInternalOrExternalDisplays() throws Exception {
when(mDisplayManagerFlagsMock.isBatteryStatsEnabledForAllDisplays()).thenReturn(true);
- verifyNoteScreenState(/* displayId= */ 2, /* expectNote= */ true);
+ for (int displayType = 0; displayType < Display.TYPE_MAX; displayType++) {
+ boolean expectNote =
+ (displayType == Display.TYPE_INTERNAL)
+ || (displayType == Display.TYPE_EXTERNAL);
+ verifyNoteScreenState(/* displayId= */ 2, displayType, expectNote);
+ }
}
- private void verifyNoteScreenState(int displayId, boolean expectNote) throws Exception {
- mHolder = createDisplayPowerController(displayId, UNIQUE_ID);
+ private void verifyNoteScreenState(int displayId, int displayDeviceType, boolean expectNote)
+ throws Exception {
+ clearInvocations(mMockBatteryStats);
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+ deviceInfo.type = displayDeviceType;
+ deviceInfo.uniqueId = UNIQUE_ID;
+ mHolder = createDisplayPowerController(displayId, deviceInfo);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -2266,17 +2387,110 @@ public final class DisplayPowerControllerTest {
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
+ final String baseErrorMessage =
+ String.format("[display id=%d type=%d]", displayId, displayDeviceType);
+ final String errorMessage;
if (expectNote) {
- verify(mMockBatteryStats)
+ errorMessage = "Expected battery stats: " + baseErrorMessage;
+ verify(mMockBatteryStats, description(errorMessage))
.noteScreenState(
displayId, Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY);
- verify(mMockBatteryStats).noteScreenBrightness(eq(displayId), anyInt());
+ verify(mMockBatteryStats, description(errorMessage))
+ .noteScreenBrightness(eq(displayId), anyInt());
} else {
- verify(mMockBatteryStats, never()).noteScreenState(anyInt(), anyInt(), anyInt());
- verify(mMockBatteryStats, never()).noteScreenBrightness(anyInt(), anyInt());
+ errorMessage = "Expected no battery stats: " + baseErrorMessage;
+ verify(mMockBatteryStats, never().description(errorMessage))
+ .noteScreenState(anyInt(), anyInt(), anyInt());
+ verify(mMockBatteryStats, never().description(errorMessage))
+ .noteScreenBrightness(anyInt(), anyInt());
}
}
+ @Test
+ public void stylusUsageStarted_disablesAutomaticBrightnessStrategy() {
+ when(mDisplayManagerFlagsMock.isBlockAutobrightnessChangesOnStylusUsage())
+ .thenReturn(true);
+ when(mDisplayManagerFlagsMock.isRefactorDisplayPowerControllerEnabled())
+ .thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ advanceTime(2);
+ clearInvocations(mHolder.automaticBrightnessController);
+ mHolder.dpc.stylusGestureStarted(2000000);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.automaticBrightnessController, times(0))
+ .getAutomaticScreenBrightness(any());
+
+ // Stylus usage timed out, hence autobrightness is now enabled back again
+ advanceTime(6);
+ verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness(null);
+
+ // Ideally we should be able to assert against new BrightnessEvent(Display.DEFAULT_DISPLAY),
+ // but because brightnessEvent has the mTime field which refers to the current time,
+ // asserting against that is non-trivial
+ verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness(
+ any(BrightnessEvent.class));
+ }
+
+ @Test
+ public void brightnessOverrideInPowerRequest_enablesOverrideBrightnessStrategy() {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.screenBrightnessOverride = OVERRIDE_BRIGHTNESS;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(OVERRIDE_BRIGHTNESS), anyFloat(), anyFloat(),
+ eq(false));
+ }
+
+ @Test
+ public void brightnessOverrideRequest_enablesOverrideBrightnessStrategy() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ var dbor = new DisplayManagerInternal.DisplayBrightnessOverrideRequest();
+ dbor.brightness = OVERRIDE_BRIGHTNESS;
+ mHolder.dpc.setBrightnessOverrideRequest(dbor);
+ advanceTime(1); // Process the WM brightness override request
+
+ verify(mHolder.animator).animateTo(eq(OVERRIDE_BRIGHTNESS), anyFloat(), anyFloat(),
+ eq(false));
+ }
+
+ @Test
+ public void onDisplayChange_canceledAfterStop() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ // stop the dpc (turn it down)
+ mHolder.dpc.stop();
+ advanceTime(1);
+
+ // To trigger all the changes that can happen, we will completely change the underlying
+ // display device.
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+ // Call onDisplayChange after we stopped DPC and make sure it doesn't crash
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ advanceTime(1);
+
+ // No crash = success
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -2308,17 +2522,16 @@ public final class DisplayPowerControllerTest {
private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
boolean isEnabled) {
-
- setUpDisplay(displayId, uniqueId, logicalDisplayMock, displayDeviceMock,
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+ deviceInfo.uniqueId = uniqueId;
+ setUpDisplay(displayId, deviceInfo, logicalDisplayMock, displayDeviceMock,
displayDeviceConfigMock, isEnabled, "display_name");
}
- private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
- DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
- boolean isEnabled, String displayName) {
+ private void setUpDisplay(int displayId, DisplayDeviceInfo deviceInfo,
+ LogicalDisplay logicalDisplayMock, DisplayDevice displayDeviceMock,
+ DisplayDeviceConfig displayDeviceConfigMock, boolean isEnabled, String displayName) {
DisplayInfo info = new DisplayInfo();
- DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- deviceInfo.uniqueId = uniqueId;
when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
@@ -2326,7 +2539,7 @@ public final class DisplayPowerControllerTest {
when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
- when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getUniqueId()).thenReturn(deviceInfo.uniqueId);
when(displayDeviceMock.getNameLocked()).thenReturn(displayName);
when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
@@ -2341,6 +2554,7 @@ public final class DisplayPowerControllerTest {
.thenReturn(new int[0]);
when(displayDeviceConfigMock.getDefaultDozeBrightness())
.thenReturn(DEFAULT_DOZE_BRIGHTNESS);
+ when(displayDeviceConfigMock.getIdleStylusTimeoutMillis()).thenReturn(5);
when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
.thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
@@ -2373,6 +2587,12 @@ public final class DisplayPowerControllerTest {
hysteresisLevels);
}
+ private DisplayPowerControllerHolder createDisplayPowerController(
+ int displayId, DisplayDeviceInfo info) {
+ return createDisplayPowerController(
+ displayId, info, /* isEnabled= */ true, /* isAutoBrightnessAvailable= */ true);
+ }
+
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
String uniqueId) {
return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
@@ -2386,6 +2606,14 @@ public final class DisplayPowerControllerTest {
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
String uniqueId, boolean isEnabled, boolean isAutoBrightnessAvailable) {
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+ deviceInfo.uniqueId = uniqueId;
+ return createDisplayPowerController(
+ displayId, deviceInfo, isEnabled, isAutoBrightnessAvailable);
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ DisplayDeviceInfo deviceInfo, boolean isEnabled, boolean isAutoBrightnessAvailable) {
final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
final AutomaticBrightnessController automaticBrightnessController =
@@ -2403,10 +2631,11 @@ public final class DisplayPowerControllerTest {
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
- when(clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
- invocation -> DisplayBrightnessState.builder()
- .setIsSlowChange(invocation.getArgument(2))
- .setBrightness(invocation.getArgument(1))
+ when(clamperController.clamp(any(), any(), anyFloat(), anyBoolean(),
+ anyInt())).thenAnswer(
+ invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState)
+ .setIsSlowChange(invocation.getArgument(3))
+ .setBrightness(invocation.getArgument(2))
.setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
.setCustomAnimationRate(-1).build());
@@ -2427,7 +2656,7 @@ public final class DisplayPowerControllerTest {
when(config.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
when(config.getScreenBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
- setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+ setUpDisplay(displayId, deviceInfo, display, device, config, isEnabled, "display_name");
when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable);
final DisplayPowerController dpc = new DisplayPowerController(
@@ -2593,7 +2822,6 @@ public final class DisplayPowerControllerTest {
BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController,
DisplayManagerFlags displayManagerFlags) {
return mAutomaticBrightnessController;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
new file mode 100644
index 000000000000..5d427139a857
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display
+
+import android.hardware.display.DisplayTopology
+import android.util.DisplayMetrics
+import android.view.Display
+import android.view.DisplayInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class DisplayTopologyCoordinatorTest {
+ private lateinit var coordinator: DisplayTopologyCoordinator
+ private val displayInfo = DisplayInfo()
+ private val topologyChangeExecutor = Runnable::run
+
+ private val mockTopology = mock<DisplayTopology>()
+ private val mockTopologyCopy = mock<DisplayTopology>()
+ private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
+ private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>()
+
+ @Before
+ fun setUp() {
+ displayInfo.displayId = 2
+ displayInfo.logicalWidth = 300
+ displayInfo.logicalHeight = 200
+ displayInfo.logicalDensityDpi = 100
+
+ val injector = object : DisplayTopologyCoordinator.Injector() {
+ override fun getTopology() = mockTopology
+ }
+ whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
+ whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
+ coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
+ mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot())
+ }
+
+ @Test
+ fun addDisplay() {
+ coordinator.onDisplayAdded(displayInfo)
+
+ val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ / displayInfo.logicalDensityDpi)
+ val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ / displayInfo.logicalDensityDpi)
+ verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
+ verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
+ }
+
+ @Test
+ fun addDisplay_extendedDisplaysDisabled() {
+ whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
+
+ coordinator.onDisplayAdded(displayInfo)
+
+ verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+ verify(mockTopologyChangedCallback, never()).invoke(any())
+ }
+
+ @Test
+ fun addDisplay_notInDefaultDisplayGroup() {
+ displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
+
+ coordinator.onDisplayAdded(displayInfo)
+
+ verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+ verify(mockTopologyChangedCallback, never()).invoke(any())
+ }
+
+ @Test
+ fun getTopology_copy() {
+ assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
+ }
+
+ @Test
+ fun setTopology_normalize() {
+ val topology = mock<DisplayTopology>()
+ val topologyCopy = mock<DisplayTopology>()
+ whenever(topology.copy()).thenReturn(topologyCopy)
+
+ coordinator.topology = topology
+
+ verify(topology).normalize()
+ verify(mockTopologyChangedCallback).invoke(topologyCopy)
+ }
+} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index f72816889c3b..782262d3f7c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@ import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.RemoteException;
import android.os.Temperature;
+import android.view.Display;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -97,6 +99,8 @@ public class ExternalDisplayPolicyTest {
@Mock
private LogicalDisplay mMockedLogicalDisplay;
@Mock
+ private LogicalDisplay mMockedDefaultDisplay;
+ @Mock
private DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock
private ExternalDisplayStatsService mMockedExternalDisplayStatsService;
@@ -141,6 +145,15 @@ public class ExternalDisplayPolicyTest {
when(mMockedLogicalDisplay.getDisplayInfoLocked()).thenReturn(mockedLogicalDisplayInfo);
when(mMockedLogicalDisplayMapper.getDisplayLocked(EXTERNAL_DISPLAY_ID)).thenReturn(
mMockedLogicalDisplay);
+
+ // Initialize default logical display
+ when(mMockedDefaultDisplay.getDisplayIdLocked()).thenReturn(Display.DEFAULT_DISPLAY);
+ when(mMockedDefaultDisplay.isEnabledLocked()).thenReturn(true);
+ final var mockedDefaultDisplayInfo = new DisplayInfo();
+ mockedDefaultDisplayInfo.type = TYPE_INTERNAL;
+ when(mMockedDefaultDisplay.getDisplayInfoLocked()).thenReturn(mockedDefaultDisplayInfo);
+ when(mMockedLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)).thenReturn(
+ mMockedDefaultDisplay);
}
@Test
@@ -293,6 +306,52 @@ public class ExternalDisplayPolicyTest {
verify(mMockedLogicalDisplayMapper, never()).forEachLocked(any());
}
+ @Test
+ public void testMirroringAlwaysConfirmedByUser_flagDisabled() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testMirroringConfirmed_afterBootForEnabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isTrue();
+ }
+
+ @Test
+ public void testMirroringNotConfirmed_afterBootForDisabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExternalDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.DEFAULT_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExistingDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.INVALID_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_duringBoot() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
private void setTemperature(final IThermalEventListener thermalEventListener,
final List<Temperature> temperature) throws RemoteException {
for (var t : temperature) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 0dd645587cc7..1eb96d2256f1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -29,8 +29,10 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -45,6 +47,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.util.Spline;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -59,6 +62,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.notifications.DisplayNotificationManager;
@@ -119,6 +123,8 @@ public class LocalDisplayAdapterTest {
private DisplayManagerFlags mFlags;
@Mock
private DisplayPowerController mMockedDisplayPowerController;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mMockedColorDisplayServiceInternal;
private Handler mHandler;
@@ -133,6 +139,11 @@ public class LocalDisplayAdapterTest {
private Injector mInjector;
@Mock
+ private DisplayDeviceConfig mMockDisplayDeviceConfig;
+ @Mock
+ private BacklightAdapter mMockBacklightAdapter;
+
+ @Mock
private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
private static final int[] BACKLIGHT_RANGE = { 1, 255 };
@@ -150,6 +161,9 @@ public class LocalDisplayAdapterTest {
doReturn(mMockedResources).when(mMockedContext).getResources();
LocalServices.removeServiceForTest(LightsManager.class);
LocalServices.addService(LightsManager.class, mMockedLightsManager);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+ LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mMockedColorDisplayServiceInternal);
mInjector = new Injector();
when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
@@ -211,7 +225,15 @@ public class LocalDisplayAdapterTest {
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
.thenReturn(new int[]{});
+
+ when(mMockedColorDisplayServiceInternal.fetchEvenDimmerSpline(3)).thenReturn(
+ new Spline.LinearSpline(
+ new float[]{2f, 3.0f, 500f, 2000f},
+ new float[]{100, 0, 0, 0}));
+ when(mMockDisplayDeviceConfig.isEvenDimmerAvailable()).thenReturn(true);
+
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
+ doReturn(true).when(mFlags).isEvenDimmerEnabled();
initDisplayOffloadSession();
}
@@ -222,6 +244,122 @@ public class LocalDisplayAdapterTest {
}
}
+ @Test
+ public void testEvenDimmer() throws InterruptedException {
+ // Set up
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // brightness|backlight| nits | strength
+ // 0.5 | 0.45 | 600 | 0 // initial setup value
+ // 0.4 | 0.35 | 500 | 0 // normal range value
+ // 0.31 | 0.2 | 3 | 0 // transition point
+ // 0.16 | 0.125 | 2.5 | 50 // mid point of even dimmer
+ // 0.1 | 0.05 | 2 | 100 // bottom of even dimmer range
+ // 0.05 | 0.01 | 1 | 100+ // beyond strength=100 range (should still return 100)
+ when(mMockDisplayDeviceConfig.getEvenDimmerTransitionPoint()).thenReturn(0.31f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.5f)).thenReturn(0.45f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.4f)).thenReturn(0.35f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.31f)).thenReturn(0.2f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.16f)).thenReturn(0.125f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.1f)).thenReturn(0.05f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.05f)).thenReturn(0.01f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.45f)).thenReturn(600f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.35f)).thenReturn(500f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.2f)).thenReturn(3f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.125f)).thenReturn(2.5f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.05f)).thenReturn(2f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.01f)).thenReturn(1f);
+
+ // initialise brightness to 0.5
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON,
+ 0.5f, 0.5f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ verify(mSurfaceControlProxy).setDisplayPowerMode(any(), anyInt());
+ verify(mMockBacklightAdapter).setBacklight(anyFloat(), anyFloat(), anyFloat(), anyFloat());
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(false), eq(0));
+ verify(mMockedColorDisplayServiceInternal).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up normal brightness range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f,
+ null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify normal brightness range
+ verify(mMockBacklightAdapter).setBacklight(0.35f, 500f, 0.35f, 500f);
+ verify(mMockedColorDisplayServiceInternal,
+ times(1)) // no more, since the strength is the same
+ .applyEvenDimmerColorChanges(eq(false), eq(0));
+ verify(mMockedColorDisplayServiceInternal, times(2)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up even dimmer edge range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.31f,
+ 0.31f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify even dimmer edge range
+ verify(mMockBacklightAdapter).setBacklight(0.2f, 3f, 0.2f, 3f);
+ // verify no more times, since the strength and enabled-ness is the same
+ verify(mMockedColorDisplayServiceInternal, times(1)).applyEvenDimmerColorChanges(eq(false),
+ eq(0));
+ verify(mMockedColorDisplayServiceInternal, times(3)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up mid point of even dimmer range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.16f,
+ 0.16f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify within even dimmer range
+ verify(mMockBacklightAdapter).setBacklight(0.125f, 2.5f, 0.125f, 2.5f);
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(50));
+ verify(mMockedColorDisplayServiceInternal, times(4)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up within even dimmer range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.1f, 0.1f,
+ null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify within even dimmer range
+ verify(mMockBacklightAdapter).setBacklight(0.05f, 2f, 0.05f, 2f);
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100));
+ verify(mMockedColorDisplayServiceInternal, times(5)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up below even dimmer range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.05f,
+ 0.05f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify within even dimmer range
+ verify(mMockBacklightAdapter).setBacklight(0.01f, 1f, 0.01f, 1f);
+ // ensure no greater than 100 strength is returned, therefore not called again.
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100));
+ verify(mMockedColorDisplayServiceInternal, times(6)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up return to normal range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f,
+ null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify return to normal range
+ verify(mMockBacklightAdapter, times(2)).setBacklight(0.35f, 500f, 0.35f, 500f);
+ verify(mMockedColorDisplayServiceInternal, times(2)).applyEvenDimmerColorChanges(eq(false),
+ anyInt());
+ verify(mMockedColorDisplayServiceInternal, times(7)).fetchEvenDimmerSpline(eq(3.0f));
+ }
+
/**
* Confirm that display is marked as private when it is listed in
* com.android.internal.R.array.config_localPrivateDisplayPorts.
@@ -1525,15 +1663,16 @@ public class LocalDisplayAdapterTest {
return mSurfaceControlProxy;
}
- // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
- // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
- // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
- // consistent behaviour. Please also note that context passed to this method, is
- // mMockContext and values will be loaded from mMockResources.
@Override
public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
- return DisplayDeviceConfig.create(context, isFirstDisplay, flags);
+ return mMockDisplayDeviceConfig;
+ }
+
+ @Override
+ public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay,
+ LocalDisplayAdapter.SurfaceControlProxy surfaceControlProxy) {
+ return mMockBacklightAdapter;
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1729ad5ff19f..ad30f22fe060 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_MIGRATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
import static android.view.Display.FLAG_REAR;
@@ -35,7 +36,9 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DE
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
import static com.android.server.utils.FoldSettingProvider.SETTING_VALUE_SELECTIVE_STAY_AWAKE;
@@ -51,6 +54,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -74,6 +78,9 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayInfo;
@@ -121,6 +128,8 @@ public class LogicalDisplayMapperTest {
Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
private static final DeviceState DEVICE_STATE_OPEN = createDeviceState(2, "Two",
Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
+ private static final DeviceState DEVICE_STATE_EMULATED = createDeviceState(3, "Three",
+ Set.of(DeviceState.PROPERTY_EMULATED_ONLY), Collections.emptySet());
private static final int FLAG_GO_TO_SLEEP_ON_FOLD = 0;
private static final int FLAG_GO_TO_SLEEP_FLAG_SOFT_SLEEP = 2;
private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
@@ -139,6 +148,9 @@ public class LogicalDisplayMapperTest {
@Rule
public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@@ -207,8 +219,8 @@ public class LogicalDisplayMapperTest {
when(mResourcesMock.getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
.thenReturn(new int[]{0});
- when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any())).thenAnswer(
- AdditionalAnswers.returnsSecondArg());
+ when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any(), anyBoolean()))
+ .thenAnswer(AdditionalAnswers.returnsSecondArg());
when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(false);
mLooper = new TestLooper();
@@ -686,6 +698,15 @@ public class LogicalDisplayMapperTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+ public void testDeviceShouldNotBeWokenWhenExitingEmulatedState() {
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
+ DEVICE_STATE_EMULATED,
+ /* isInteractive= */false,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldBePutToSleep() {
assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
DEVICE_STATE_OPEN,
@@ -1147,6 +1168,29 @@ public class LogicalDisplayMapperTest {
mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
}
+ @Test
+ public void updateAndGetMaskForDisplayPropertyChanges_getsPropertyChangedFlags() {
+ // Change the display state
+ DisplayInfo newDisplayInfo = new DisplayInfo();
+ newDisplayInfo.state = STATE_OFF;
+ assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
+ mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+
+ // Change the refresh rate override
+ newDisplayInfo = new DisplayInfo();
+ newDisplayInfo.refreshRateOverride = 30;
+ assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED,
+ mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+
+ // Change multiple properties
+ newDisplayInfo = new DisplayInfo();
+ newDisplayInfo.refreshRateOverride = 30;
+ newDisplayInfo.state = STATE_OFF;
+ assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED
+ | LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
+ mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+ }
+
/////////////////
// Helper Methods
/////////////////
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 8936f061963c..241dc10747ac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -16,11 +16,14 @@
package com.android.server.display;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -87,7 +90,7 @@ public class LogicalDisplayTest {
mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
- when(mSyntheticModeManager.createAppSupportedModes(any(), any())).thenAnswer(
+ when(mSyntheticModeManager.createAppSupportedModes(any(), any(), anyBoolean())).thenAnswer(
AdditionalAnswers.returnsSecondArg());
// Disable binder caches in this process.
@@ -326,6 +329,23 @@ public class LogicalDisplayTest {
}
@Test
+ public void testBrightnessConfigurationFromDisplayDevice() {
+ mDisplayDeviceInfo.brightnessMinimum = 0.12f;
+ mDisplayDeviceInfo.brightnessDim = 0.34f;
+ mDisplayDeviceInfo.brightnessDefault = 0.56f;
+ mDisplayDeviceInfo.brightnessMaximum = 0.78f;
+
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
+ mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
+
+ DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
+ assertThat(info.brightnessMinimum).isEqualTo(0.12f);
+ assertThat(info.brightnessDim).isEqualTo(0.34f);
+ assertThat(info.brightnessDefault).isEqualTo(0.56f);
+ assertThat(info.brightnessMaximum).isEqualTo(0.78f);
+ }
+
+ @Test
public void testGetDisplayPosition() {
Point expectedPosition = new Point(0, 0);
@@ -582,7 +602,8 @@ public class LogicalDisplayTest {
Display.Mode[] appSupportedModes = new Display.Mode[] {new Display.Mode(OTHER_MODE_ID,
DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 45)};
when(mSyntheticModeManager.createAppSupportedModes(
- any(), eq(mDisplayDeviceInfo.supportedModes))).thenReturn(appSupportedModes);
+ any(), eq(mDisplayDeviceInfo.supportedModes), anyBoolean()))
+ .thenReturn(appSupportedModes);
mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 81e6cc3f546b..dbd5c65f9ba3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -16,80 +16,178 @@
package com.android.server.display;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjection;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.Process;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.testing.TestableContext;
+import android.view.Display;
+import android.view.Surface;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class VirtualDisplayAdapterTest {
+ private static final int MAX_DEVICES = 3;
+ private static final int MAX_DEVICES_PER_PACKAGE = 2;
+
+ private static final float DEFAULT_BRIGHTNESS = 0.34f;
+ private static final float DIM_BRIGHTNESS = 0.12f;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
@Mock
- Context mContextMock;
+ private VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory;
@Mock
- VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory;
+ private DisplayAdapter.Listener mMockListener;
@Mock
- DisplayAdapter.Listener mMockListener;
+ private IVirtualDisplayCallback mMockCallback;
@Mock
- IVirtualDisplayCallback mMockCallback;
+ private IBinder mMockBinder;
@Mock
- IBinder mMockBinder;
+ private IMediaProjection mMediaProjectionMock;
- private TestHandler mHandler;
+ @Mock
+ private Surface mSurfaceMock;
+
+ @Mock
+ private VirtualDisplayConfig mVirtualDisplayConfigMock;
- private VirtualDisplayAdapter mVirtualDisplayAdapter;
+ private TestHandler mHandler;
@Mock
private DisplayManagerFlags mFeatureFlags;
+ private VirtualDisplayAdapter mAdapter;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ mContext.getOrCreateTestableResources().addOverride(R.integer.config_virtualDisplayLimit,
+ MAX_DEVICES);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_virtualDisplayLimitPerPackage, MAX_DEVICES_PER_PACKAGE);
+
mHandler = new TestHandler(null);
- mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(),
- mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory,
- mFeatureFlags);
+ mAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(), mContext,
+ mHandler, mMockListener, mMockSufaceControlDisplayFactory, mFeatureFlags);
when(mMockCallback.asBinder()).thenReturn(mMockBinder);
}
@Test
- public void testCreatesVirtualDisplay() {
+ public void testCreateAndReleaseVirtualDisplay() {
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
/* height= */ 1, /* densityDpi= */ 1).build();
+ int ownerUid = 10;
- DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback,
- /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ DisplayDevice result = mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, ownerUid, /* packageName= */ "testpackage",
/* uniqueId= */ "uniqueId", /* surface= */ null, /* flags= */ 0, config);
+ assertNotNull(result);
+ result = mAdapter.releaseVirtualDisplayLocked(mMockBinder, ownerUid);
assertNotNull(result);
}
@Test
+ public void testCreateVirtualDisplay_createDisplayDeviceInfoFromDefaults() {
+ VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+ "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+ .build();
+
+ final String packageName = "testpackage";
+ final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+ packageName, Process.myUid(), config);
+
+ DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+ mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+ packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+ assertNotNull(displayDevice);
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+ assertNotNull(info);
+
+ assertThat(info.width).isEqualTo(640);
+ assertThat(info.height).isEqualTo(480);
+ assertThat(info.densityDpi).isEqualTo(240);
+ assertThat(info.xDpi).isEqualTo(240);
+ assertThat(info.yDpi).isEqualTo(240);
+ assertThat(info.name).isEqualTo("testDisplayName");
+ assertThat(info.uniqueId).isEqualTo(displayUniqueId);
+ assertThat(info.ownerPackageName).isEqualTo(packageName);
+ assertThat(info.ownerUid).isEqualTo(10);
+ assertThat(info.type).isEqualTo(Display.TYPE_VIRTUAL);
+ assertThat(info.brightnessMinimum).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+ assertThat(info.brightnessMaximum).isEqualTo(PowerManager.BRIGHTNESS_MAX);
+ assertThat(info.brightnessDefault).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+ assertThat(info.brightnessDim).isEqualTo(PowerManager.BRIGHTNESS_INVALID);
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_createDisplayDeviceInfoFromVirtualDisplayConfig() {
+ VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+ "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+ .setDefaultBrightness(DEFAULT_BRIGHTNESS)
+ .setDimBrightness(DIM_BRIGHTNESS)
+ .build();
+
+ final String packageName = "testpackage";
+ final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+ packageName, Process.myUid(), config);
+
+ DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+ mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+ packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+ assertNotNull(displayDevice);
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+ assertNotNull(info);
+
+ assertThat(info.brightnessDefault).isEqualTo(DEFAULT_BRIGHTNESS);
+ assertThat(info.brightnessDim).isEqualTo(DIM_BRIGHTNESS);
+ }
+
+ @Test
public void testCreatesVirtualDisplay_checkGeneratedDisplayUniqueIdPrefix() {
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
/* height= */ 1, /* densityDpi= */ 1).build();
@@ -98,7 +196,7 @@ public class VirtualDisplayAdapterTest {
final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
packageName, Process.myUid(), config);
- DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(
+ DisplayDevice result = mAdapter.createVirtualDisplayLocked(
mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
@@ -114,14 +212,194 @@ public class VirtualDisplayAdapterTest {
/* height= */ 1, /* densityDpi= */ 1).build();
VirtualDisplayConfig config2 = new VirtualDisplayConfig.Builder("test2", /* width= */ 1,
/* height= */ 1, /* densityDpi= */ 1).build();
- mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, /* projection= */ null,
- /* ownerUid= */ 10, /* packageName= */ "testpackage", /* uniqueId= */ "uniqueId1",
- /* surface= */ null, /* flags= */ 0, config1);
- DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback,
+ DisplayDevice result = mAdapter.createVirtualDisplayLocked(mMockCallback,
/* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
- /* uniqueId= */ "uniqueId2", /* surface= */ null, /* flags= */ 0, config2);
+ /* uniqueId= */ "uniqueId1", /* surface= */ null, /* flags= */ 0, config1);
+ assertNotNull(result);
+ result = mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ /* uniqueId= */ "uniqueId2", /* surface= */ null, /* flags= */ 0, config2);
assertNull(result);
}
+
+ @Test
+ public void testCreateManyVirtualDisplays_LimitFlagDisabled() {
+ when(mFeatureFlags.isVirtualDisplayLimitEnabled()).thenReturn(false);
+
+ // Displays for the same package
+ for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) {
+ // Same owner UID
+ IVirtualDisplayCallback callback = createCallback();
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback,
+ mMediaProjectionMock, 1234, "test.package", "123",
+ mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock);
+ assertNotNull(device);
+ }
+
+ // Displays for different packages
+ for (int i = 0; i < MAX_DEVICES * 2; i++) {
+ // Same owner UID
+ IVirtualDisplayCallback callback = createCallback();
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback,
+ mMediaProjectionMock, 1234 + i, "test.package", "123",
+ mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock);
+ assertNotNull(device);
+ }
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_MaxDisplaysPerPackage() {
+ when(mFeatureFlags.isVirtualDisplayLimitEnabled()).thenReturn(true);
+ List<IVirtualDisplayCallback> callbacks = new ArrayList<>();
+ int ownerUid = 1234;
+
+ for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) {
+ // Same owner UID
+ IVirtualDisplayCallback callback = createCallback();
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback,
+ mMediaProjectionMock, ownerUid, "test.package", "123",
+ mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock);
+ if (i < MAX_DEVICES_PER_PACKAGE) {
+ assertNotNull(device);
+ callbacks.add(callback);
+ } else {
+ assertNull(device);
+ }
+ }
+
+ // Release one display
+ DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder(),
+ ownerUid);
+ assertNotNull(device);
+ callbacks.remove(0);
+
+ // We should be able to create another display
+ IVirtualDisplayCallback callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid,
+ "test.package", "123", mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+ assertNotNull(device);
+ callbacks.add(callback);
+
+ // But only one
+ callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid,
+ "test.package", "123", mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+ assertNull(device);
+
+ // Release all the displays
+ for (IVirtualDisplayCallback cb : callbacks) {
+ device = mAdapter.releaseVirtualDisplayLocked(cb.asBinder(), ownerUid);
+ assertNotNull(device);
+ }
+ callbacks.clear();
+
+ // We should be able to create the max number of displays again
+ for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) {
+ // Same owner UID
+ callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid,
+ "test.package", "123", mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+ if (i < MAX_DEVICES_PER_PACKAGE) {
+ assertNotNull(device);
+ callbacks.add(callback);
+ } else {
+ assertNull(device);
+ }
+ }
+
+ // We should be able to create a display for a different package
+ callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid + 1,
+ "test.package", "123", mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+ assertNotNull(device);
+ callbacks.add(callback);
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_MaxDisplays() {
+ when(mFeatureFlags.isVirtualDisplayLimitEnabled()).thenReturn(true);
+ List<IVirtualDisplayCallback> callbacks = new ArrayList<>();
+ int firstOwnerUid = 1000;
+
+ for (int i = 0; i < MAX_DEVICES * 2; i++) {
+ // Different owner UID
+ IVirtualDisplayCallback callback = createCallback();
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback,
+ mMediaProjectionMock, firstOwnerUid + i, "test.package",
+ "123", mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock);
+ if (i < MAX_DEVICES) {
+ assertNotNull(device);
+ callbacks.add(callback);
+ } else {
+ assertNull(device);
+ }
+ }
+
+ // Release one display
+ DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder(),
+ firstOwnerUid);
+ assertNotNull(device);
+ callbacks.remove(0);
+
+ // We should be able to create another display
+ IVirtualDisplayCallback callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock,
+ firstOwnerUid, "test.package", "123", mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+ assertNotNull(device);
+ callbacks.add(callback);
+
+ // But only one
+ callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock,
+ firstOwnerUid, "test.package", "123", mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+ assertNull(device);
+
+ // Release all the displays
+ for (int i = 0; i < callbacks.size(); i++) {
+ device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(i).asBinder(),
+ firstOwnerUid + i);
+ assertNotNull(device);
+ }
+ callbacks.clear();
+
+ // We should be able to create the max number of displays again
+ for (int i = 0; i < MAX_DEVICES * 2; i++) {
+ // Different owner UID
+ callback = createCallback();
+ device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock,
+ firstOwnerUid + i, "test.package", "123", mSurfaceMock,
+ /* flags= */ 0, mVirtualDisplayConfigMock);
+ if (i < MAX_DEVICES) {
+ assertNotNull(device);
+ callbacks.add(callback);
+ } else {
+ assertNull(device);
+ }
+ }
+ }
+
+ private IVirtualDisplayCallback createCallback() {
+ return new IVirtualDisplayCallback.Stub() {
+
+ @Override
+ public void onPaused() {
+ }
+
+ @Override
+ public void onResumed() {
+ }
+
+ @Override
+ public void onStopped() {
+ }
+ };
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index 04b79b4f1761..d93ee84f4870 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -74,7 +74,7 @@ public final class BrightnessReasonTest {
@Test
public void setModifierDoesntSetIfModifierIsBeyondExtremes() {
- int extremeModifier = 0x40; // equal to BrightnessReason.MODIFIER_MASK * 2
+ int extremeModifier = 0x80;
// reset modifier
mBrightnessReason.setModifier(0);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index c0698756a3d7..49de80179683 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -52,6 +52,7 @@ import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStra
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -121,11 +122,12 @@ public final class DisplayBrightnessControllerTest {
int targetDisplayState = Display.STATE_DOZE;
when(mDisplayBrightnessStrategySelector.selectStrategy(
any(StrategySelectionRequest.class))).thenReturn(displayBrightnessStrategy);
- mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState, mock(
- DisplayManagerInternal.DisplayOffloadSession.class));
+ mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState,
+ mock(DisplayManagerInternal.DisplayOffloadSession.class),
+ /* isBedtimeModeWearEnabled= */ false);
verify(displayBrightnessStrategy).updateBrightness(
eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS,
- /* userSetBrightnessChanged= */ false)));
+ /* userSetBrightnessChanged= */ false, /* isStylusBeingUsed */ false)));
assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
displayBrightnessStrategy);
}
@@ -154,6 +156,27 @@ public final class DisplayBrightnessControllerTest {
}
@Test
+ public void updateWindowManagerBrightnessOverride() {
+ var request = new DisplayManagerInternal.DisplayBrightnessOverrideRequest();
+ request.brightness = 0.4f;
+ request.tag = "cts";
+ OverrideBrightnessStrategy overrideBrightnessStrategy = mock(
+ OverrideBrightnessStrategy.class);
+ when(mDisplayBrightnessStrategySelector.getOverrideBrightnessStrategy()).thenReturn(
+ overrideBrightnessStrategy);
+
+ when(overrideBrightnessStrategy.updateWindowManagerBrightnessOverride(any()))
+ .thenReturn(false);
+ assertFalse(mDisplayBrightnessController.updateWindowManagerBrightnessOverride(request));
+ verify(overrideBrightnessStrategy).updateWindowManagerBrightnessOverride(request);
+
+ when(overrideBrightnessStrategy.updateWindowManagerBrightnessOverride(any()))
+ .thenReturn(true);
+ assertTrue(mDisplayBrightnessController.updateWindowManagerBrightnessOverride(request));
+ verify(overrideBrightnessStrategy, times(2)).updateWindowManagerBrightnessOverride(request);
+ }
+
+ @Test
public void setCurrentScreenBrightness() {
// Current Screen brightness is set as expected when a different value than the current
// is set
@@ -559,4 +582,11 @@ public final class DisplayBrightnessControllerTest {
displayDeviceConfig, handler, brightnessMappingStrategy, isDisplayEnabled,
leadDisplayId);
}
+
+ @Test
+ public void setStylusBeingUsed_setsStylusInUseState() {
+ assertFalse(mDisplayBrightnessController.isStylusBeingUsed());
+ mDisplayBrightnessController.setStylusBeingUsed(true);
+ assertTrue(mDisplayBrightnessController.isStylusBeingUsed());
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index a44c517ed9cf..2ebb6c2a3ce4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public final class DisplayBrightnessStrategySelectorTest {
private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
+ private static final boolean STYLUS_IS_NOT_BEING_USED = false;
+ private static final boolean STYLUS_IS_BEING_USED = true;
private static final int DISPLAY_ID = 1;
@Mock
@@ -181,6 +183,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mContext.getContentResolver()).thenReturn(contentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
+ when(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
mInjector, DISPLAY_ID, mDisplayManagerFlags);
@@ -196,13 +200,15 @@ public final class DisplayBrightnessStrategySelectorTest {
DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mDozeBrightnessModeStrategy);
}
@Test
public void selectStrategyWhenValid_useNormalBrightnessForDozeTrue_doNotSelectsDozeStrategy() {
- when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)).thenReturn(
+ true);
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
@@ -212,7 +218,8 @@ public final class DisplayBrightnessStrategySelectorTest {
DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mDozeBrightnessModeStrategy);
}
@@ -226,7 +233,8 @@ public final class DisplayBrightnessStrategySelectorTest {
DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mDozeBrightnessModeStrategy);
}
@@ -249,7 +257,8 @@ public final class DisplayBrightnessStrategySelectorTest {
assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mDozeBrightnessModeStrategy);
}
@@ -259,7 +268,8 @@ public final class DisplayBrightnessStrategySelectorTest {
DisplayManagerInternal.DisplayPowerRequest.class);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mScreenOffBrightnessModeStrategy);
}
@@ -271,7 +281,22 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
+ mOverrideBrightnessStrategy);
+ }
+
+ @Test
+ public void selectStrategySelectsOverrideStrategyWhenWindowManagerOverrideIsValid() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+ when(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()).thenReturn(0.4f);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mOverrideBrightnessStrategy);
}
@@ -284,7 +309,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mTemporaryBrightnessStrategy);
}
@@ -298,7 +324,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mBoostBrightnessStrategy);
}
@@ -312,7 +339,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mInvalidBrightnessStrategy);
}
@@ -323,7 +351,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mFollowerBrightnessStrategy);
}
@@ -341,7 +370,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mOffloadBrightnessStrategy);
}
@@ -365,12 +395,40 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mAutomaticBrightnessStrategy);
verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON,
true, BrightnessReason.REASON_UNKNOWN,
DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
- /* useNormalBrightnessForDoze= */ false, 0.1f, false);
+ /* useNormalBrightnessForDoze= */ false, 0.1f, false,
+ /* isBedtimeModeWearEnabled= */ false);
+ }
+
+
+ @Test
+ public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() {
+ when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
+ when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
+ when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+ true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
+ when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true);
+ assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
+ mAutomaticBrightnessStrategy);
}
@Test
@@ -389,7 +447,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mAutoBrightnessFallbackStrategy.isValid()).thenReturn(true);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mAutoBrightnessFallbackStrategy);
}
@@ -407,7 +466,8 @@ public final class DisplayBrightnessStrategySelectorTest {
assertNotEquals(mOffloadBrightnessStrategy,
mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)));
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)));
}
@Test
@@ -425,7 +485,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession)),
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)),
mFallbackBrightnessStrategy);
}
@@ -440,12 +501,14 @@ public final class DisplayBrightnessStrategySelectorTest {
mDisplayBrightnessStrategySelector.selectStrategy(
new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
- 0.1f, false, mDisplayOffloadSession));
+ 0.1f, false, mDisplayOffloadSession,
+ STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false));
StrategySelectionNotifyRequest strategySelectionNotifyRequest =
new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON,
mFollowerBrightnessStrategy, 0.1f,
- false, false, false);
+ false, false, false,
+ /* isBedtimeModeWearEnabled= */ false);
for (DisplayBrightnessStrategy displayBrightnessStrategy :
mDisplayBrightnessStrategySelector.mDisplayBrightnessStrategies) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index da79f301ee3c..238654d2aaf1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -19,13 +19,15 @@ package com.android.server.display.brightness.clamper;
import static android.view.Display.STATE_OFF;
import static android.view.Display.STATE_ON;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,7 +35,6 @@ import android.content.Context;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
-import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.testing.TestableContext;
@@ -83,8 +84,6 @@ public class BrightnessClamperControllerTest {
@Mock
private LightSensorController mMockLightSensorController;
@Mock
- private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
- @Mock
private DisplayManagerFlags mFlags;
@Mock
private BrightnessModifier mMockModifier;
@@ -93,22 +92,27 @@ public class BrightnessClamperControllerTest {
@Mock
private TestDisplayListenerModifier mMockDisplayListenerModifier;
@Mock
+ private TestDeviceConfigListenerModifier mMockDeviceConfigListenerModifier;
+ @Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
@Mock
private DeviceConfig.Properties mMockProperties;
private BrightnessClamperController mClamperController;
+ private DisplayBrightnessState mDisplayBrightnessState;
private TestInjector mTestInjector;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTestInjector = new TestInjector(List.of(mMockClamper),
- List.of(mMockModifier, mMockStatefulModifier, mMockDisplayListenerModifier));
+ mTestInjector = new TestInjector(
+ List.of(mMockModifier, mMockStatefulModifier,
+ mMockDisplayListenerModifier, mMockDeviceConfigListenerModifier));
when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID);
when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
mClamperController = createBrightnessClamperController();
+ mDisplayBrightnessState = DisplayBrightnessState.builder().build();
}
@Test
@@ -123,16 +127,11 @@ public class BrightnessClamperControllerTest {
@Test
public void testConstructor_doesNotStartsLightSensorController() {
- verify(mMockLightSensorController, never()).restart();
- }
-
- @Test
- public void testConstructor_startsLightSensorController() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
mClamperController = createBrightnessClamperController();
- verify(mMockLightSensorController).restart();
+ verify(mMockLightSensorController, never()).restart();
}
@Test
@@ -148,7 +147,7 @@ public class BrightnessClamperControllerTest {
}
@Test
- public void testDelegatesPropertiesChangeToClamper() {
+ public void testDelegatesPropertiesChangeToDeviceConfigLisener() {
ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> captor = ArgumentCaptor.forClass(
DeviceConfig.OnPropertiesChangedListener.class);
verify(mMockDeviceConfigParameterProvider)
@@ -156,14 +155,7 @@ public class BrightnessClamperControllerTest {
captor.getValue().onPropertiesChanged(mMockProperties);
- verify(mMockClamper).onDeviceConfigChanged();
- }
-
- @Test
- public void testOnDisplayChanged_DelegatesToClamper() {
- mClamperController.onDisplayChanged(mMockDisplayDeviceData);
-
- verify(mMockClamper).onDisplayChanged(mMockDisplayDeviceData);
+ verify(mMockDeviceConfigListenerModifier).onDeviceConfigChanged();
}
@Test
@@ -175,24 +167,48 @@ public class BrightnessClamperControllerTest {
@Test
public void testOnDisplayChanged_doesNotRestartLightSensor() {
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
}
@Test
public void testOnDisplayChanged_restartsLightSensor() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+ verify(mMockLightSensorController, never()).stop();
verify(mMockLightSensorController).restart();
}
@Test
+ public void testOnDisplayChanged_doesNotRestartLightSensor_screenOff() {
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_OFF);
+ reset(mMockLightSensorController);
+
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
+ }
+
+ @Test
public void testClamp_AppliesModifier() {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
- mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
+ initialSlowChange, STATE_ON);
verify(mMockModifier).apply(eq(mMockRequest), any());
verify(mMockDisplayListenerModifier).apply(eq(mMockRequest), any());
@@ -204,7 +220,8 @@ public class BrightnessClamperControllerTest {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
- mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
+ initialSlowChange, STATE_ON);
verify(mMockLightSensorController).restart();
}
@@ -214,108 +231,26 @@ public class BrightnessClamperControllerTest {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
clearInvocations(mMockLightSensorController);
- mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_OFF);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
+ initialSlowChange, STATE_OFF);
verify(mMockLightSensorController).stop();
}
@Test
- public void testClamp_inactiveClamperNotApplied() {
- float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.6f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(false);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
-
- DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
-
- assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(0,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertEquals(initialSlowChange, state.isSlowChange());
- }
-
- @Test
- public void testClamp_activeClamperApplied_brightnessAboveMax() {
+ public void testClamp_activeClamperApplied_confirmBrightnessOverrideStateReturned() {
float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.6f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(true);
+ boolean initialSlowChange = false;
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
- DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
-
- assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(BrightnessReason.MODIFIER_THROTTLED,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertFalse(state.isSlowChange());
- }
-
- @Test
- public void testClamp_activeClamperApplied_brightnessBelowMax() {
- float initialBrightness = 0.6f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.8f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(true);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
+ mDisplayBrightnessState = DisplayBrightnessState.builder().setBrightnessReason(
+ BrightnessReason.REASON_OVERRIDE).build();
- DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
+ DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
+ mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
- assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(BrightnessReason.MODIFIER_THROTTLED,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertFalse(state.isSlowChange());
- }
-
- @Test
- public void testClamp_activeClamperAppliedTwoTimes_keepsSlowChange() {
- float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.6f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(true);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
- // first call of clamp method
- mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
- // immediately second call of clamp method
- DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
-
- assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(BrightnessReason.MODIFIER_THROTTLED,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertEquals(initialSlowChange, state.isSlowChange());
+ assertEquals(BrightnessReason.REASON_OVERRIDE, state.getBrightnessReason().getReason());
}
@Test
@@ -331,7 +266,6 @@ public class BrightnessClamperControllerTest {
mClamperController.stop();
verify(mMockLightSensorController).stop();
verify(mMockModifier).stop();
- verify(mMockClamper).stop();
}
@Test
@@ -356,6 +290,24 @@ public class BrightnessClamperControllerTest {
verify(mMockExternalListener).onChanged();
}
+ @Test
+ public void test_doesNotScheduleRecalculateBeforeStart() {
+ mTestInjector = new TestInjector(List.of()) {
+ @Override
+ List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
+ Handler handler, BrightnessClamperController.ClamperChangeListener listener,
+ BrightnessClamperController.DisplayDeviceData displayDeviceData,
+ float currentBrightness) {
+ listener.onChanged();
+ return super.getModifiers(flags, context, handler, listener, displayDeviceData,
+ currentBrightness);
+ }
+ };
+ mClamperController = createBrightnessClamperController();
+
+ assertThat(mTestHandler.getPendingMessages()).isEmpty();
+ }
+
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager, 0);
@@ -369,20 +321,18 @@ public class BrightnessClamperControllerTest {
BrightnessClamperController.StatefulModifier {
}
+ interface TestDeviceConfigListenerModifier extends BrightnessStateModifier,
+ BrightnessClamperController.DeviceConfigListener {
+
+ }
+
private class TestInjector extends BrightnessClamperController.Injector {
- private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
- mClampers;
private final List<BrightnessStateModifier> mModifiers;
-
private BrightnessClamperController.ClamperChangeListener mCapturedChangeListener;
private LightSensorController.LightSensorListener mCapturedLightSensorListener;
- private TestInjector(
- List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
- clampers,
- List<BrightnessStateModifier> modifiers) {
- mClampers = clampers;
+ private TestInjector(List<BrightnessStateModifier> modifiers) {
mModifiers = modifiers;
}
@@ -392,19 +342,11 @@ public class BrightnessClamperControllerTest {
}
@Override
- List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
- Handler handler,
- BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- BrightnessClamperController.DisplayDeviceData data,
- DisplayManagerFlags flags, Context context, float currentBrightness) {
- mCapturedChangeListener = clamperChangeListener;
- return mClampers;
- }
-
- @Override
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, BrightnessClamperController.ClamperChangeListener listener,
- BrightnessClamperController.DisplayDeviceData displayDeviceData) {
+ BrightnessClamperController.DisplayDeviceData displayDeviceData,
+ float currentBrightness) {
+ mCapturedChangeListener = listener;
return mModifiers;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
deleted file mode 100644
index c4898da62d81..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.brightness.clamper;
-
-import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.Temperature;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
-import com.android.server.testutils.FakeDeviceConfigInterface;
-import com.android.server.testutils.TestHandler;
-
-import junitparams.JUnitParamsRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@RunWith(JUnitParamsRunner.class)
-public class BrightnessPowerClamperTest {
- private static final String TAG = "BrightnessPowerClamperTest";
- private static final float FLOAT_TOLERANCE = 0.001f;
-
- private static final String DISPLAY_ID = "displayId";
- @Mock
- private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
- private TestPmicMonitor mPmicMonitor;
- private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
- new FakeDeviceConfigInterface();
- private final TestHandler mTestHandler = new TestHandler(null);
- private final TestInjector mTestInjector = new TestInjector();
- private BrightnessPowerClamper mClamper;
- private final float mCurrentBrightness = 0.6f;
- private PowerChangeListener mPowerChangeListener;
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mClamper = new BrightnessPowerClamper(mTestInjector, mTestHandler,
- mMockClamperChangeListener, new TestPowerData(), mCurrentBrightness);
- mPowerChangeListener = mClamper.getPowerChangeListener();
- mPmicMonitor = mTestInjector.getPmicMonitor(mPowerChangeListener, null, 5, 10);
- mPmicMonitor.setPowerChangeListener(mPowerChangeListener);
- mTestHandler.flush();
- }
-
- @Test
- public void testTypeIsPower() {
- assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
- }
-
- @Test
- public void testNoThrottlingData() {
- assertFalse(mClamper.isActive());
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
- mTestHandler.flush();
- assertFalse(mClamper.isActive());
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
- // update a new device config for power-throttling.
- mClamper.onDisplayChanged(new TestPowerData(
- List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
-
- mPmicMonitor.setAvgPowerConsumed(200f);
- float expectedBrightness = 0.5f;
- expectedBrightness = expectedBrightness * mCurrentBrightness;
-
- mTestHandler.flush();
- // Assume current brightness as max, as there is no throttling.
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
- mTestHandler.flush();
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
- // update a new device config for power-throttling.
- mClamper.onDisplayChanged(new TestPowerData(
- List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
-
- mPmicMonitor.setAvgPowerConsumed(200f);
- float expectedBrightness = 0.5f;
- expectedBrightness = expectedBrightness * mCurrentBrightness;
- mTestHandler.flush();
- // Assume current brightness as max, as there is no throttling.
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
- mTestHandler.flush();
- assertFalse(mClamper.isActive());
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
- // update a new device config for power-throttling.
- mClamper.onDisplayChanged(new TestPowerData(
- List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
-
- mPmicMonitor.setAvgPowerConsumed(200f);
- float expectedBrightness = 0.5f;
- expectedBrightness = expectedBrightness * mCurrentBrightness;
- mTestHandler.flush();
-
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
-
- mPmicMonitor.setAvgPowerConsumed(100f);
- // No cap applied for Temperature.THROTTLING_NONE
- expectedBrightness = PowerManager.BRIGHTNESS_MAX;
- mTestHandler.flush();
-
- // clamper should not be active anymore.
- assertFalse(mClamper.isActive());
- // Assume current brightness as max, as there is no throttling.
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
-
- private static class TestPmicMonitor extends PmicMonitor {
- private Temperature mCurrentTemperature;
- private PowerChangeListener mListener;
- TestPmicMonitor(PowerChangeListener listener,
- IThermalService thermalService,
- int pollingTimeMax, int pollingTimeMin) {
- super(listener, thermalService, pollingTimeMax, pollingTimeMin);
- }
- public void setAvgPowerConsumed(float power) {
- int status = mCurrentTemperature.getStatus();
- mListener.onChanged(power, status);
- }
- public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
- mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
- }
- public void setPowerChangeListener(PowerChangeListener listener) {
- mListener = listener;
- }
- }
-
- private class TestInjector extends BrightnessPowerClamper.Injector {
- @Override
- TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
- IThermalService thermalService,
- int minPollingTimeMillis, int maxPollingTimeMillis) {
- mPmicMonitor = new TestPmicMonitor(listener, thermalService, maxPollingTimeMillis,
- minPollingTimeMillis);
- return mPmicMonitor;
- }
-
- @Override
- DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
- return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
- }
- }
-
- private static class TestPowerData implements BrightnessPowerClamper.PowerData {
-
- private final String mUniqueDisplayId;
- private final String mDataId;
- private final PowerThrottlingData mData;
- private final PowerThrottlingConfigData mConfigData;
-
- private TestPowerData() {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
- }
-
- private TestPowerData(List<ThrottlingLevel> data) {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
- }
-
- private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
- mUniqueDisplayId = uniqueDisplayId;
- mDataId = dataId;
- mData = PowerThrottlingData.create(data);
- mConfigData = new PowerThrottlingConfigData(0.1f, 10, 20, 10);
- }
-
- @NonNull
- @Override
- public String getUniqueDisplayId() {
- return mUniqueDisplayId;
- }
-
- @NonNull
- @Override
- public String getPowerThrottlingDataId() {
- return mDataId;
- }
-
- @Nullable
- @Override
- public PowerThrottlingData getPowerThrottlingData() {
- return mData;
- }
-
- @Nullable
- @Override
- public PowerThrottlingConfigData getPowerThrottlingConfigData() {
- return mConfigData;
- }
- }
-}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java
new file mode 100644
index 000000000000..b438d745806c
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static android.os.PowerManager.BRIGHTNESS_MAX;
+
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+import static com.android.server.display.brightness.clamper.BrightnessPowerModifier.PowerChangeListener;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerModifierTest {
+ private static final String DISPLAY_ID = "displayId";
+ private static final int NO_MODIFIER = 0;
+ private static final float CUSTOM_ANIMATION_RATE = 10f;
+ private static final PowerThrottlingConfigData DEFAULT_CONFIG = new PowerThrottlingConfigData(
+ 0.1f, CUSTOM_ANIMATION_RATE, 20, 10);
+ private static final float DEFAULT_BRIGHTNESS = 0.6f;
+
+ @Mock
+ private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+ @Mock
+ private DisplayDeviceConfig mMockDisplayDeviceConfig;
+ @Mock
+ private IBinder mMockBinder;
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+ private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+ new FakeDeviceConfigInterface();
+ private final TestHandler mTestHandler = new TestHandler(null);
+ private final TestInjector mTestInjector = new TestInjector();
+ private BrightnessPowerModifier mModifier;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockDisplayDeviceConfig.getPowerThrottlingConfigData()).thenReturn(DEFAULT_CONFIG);
+ mModifier = new BrightnessPowerModifier(mTestInjector, mTestHandler,
+ mMockClamperChangeListener, ClamperTestUtilsKt.createDisplayDeviceData(
+ mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID,
+ DisplayDeviceConfig.DEFAULT_ID), DEFAULT_BRIGHTNESS);
+ mTestHandler.flush();
+ }
+
+ @Test
+ public void testNoThrottlingData() {
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+ }
+
+ @Test
+ public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+ mTestHandler.flush();
+ // no config yet, modifier inactive
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+ // update a new device config for power-throttling.
+ float powerQuota = 100f;
+ float avgPowerConsumed = 200f;
+ onDisplayChanged(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, powerQuota)));
+ mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(avgPowerConsumed);
+
+ float expectedBrightnessCap = (powerQuota / avgPowerConsumed) * DEFAULT_BRIGHTNESS;
+ mTestHandler.flush();
+
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ expectedBrightnessCap, expectedBrightnessCap, CUSTOM_ANIMATION_RATE, true);
+ }
+
+ @Test
+ public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+ mTestHandler.flush();
+ // no config yet, modifier inactive
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+ // update a new device config for power-throttling.
+ float powerQuota = 100f;
+ float avgPowerConsumed = 200f;
+ onDisplayChanged(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, powerQuota)));
+
+ mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(avgPowerConsumed);
+ float expectedBrightnessCap = (powerQuota / avgPowerConsumed) * DEFAULT_BRIGHTNESS;
+ mTestHandler.flush();
+ // Assume current brightness as max, as there is no throttling.
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ expectedBrightnessCap, expectedBrightnessCap, CUSTOM_ANIMATION_RATE, true);
+ }
+
+ @Test
+ public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+ mTestHandler.flush();
+ // no config yet, modifier inactive
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+ // update a new device config for power-throttling.
+ onDisplayChanged(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f)));
+ mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(200f);
+
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+ // No cap applied for Temperature.THROTTLING_NONE
+ mTestHandler.flush();
+
+ // Modifier should not be active anymore, no throttling
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+ }
+
+ private void onDisplayChanged(List<ThrottlingLevel> throttlingLevels) {
+ Map<String, PowerThrottlingData> throttlingLevelsMap = new HashMap<>();
+ throttlingLevelsMap.put(DisplayDeviceConfig.DEFAULT_ID,
+ PowerThrottlingData.create(throttlingLevels));
+ when(mMockDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId())
+ .thenReturn(throttlingLevelsMap);
+ mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData(
+ mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID,
+ DisplayDeviceConfig.DEFAULT_ID));
+ }
+
+ private void assertModifierState(
+ float currentBrightness,
+ float maxBrightness, float brightness, float customAnimationRate,
+ boolean isActive) {
+ ModifiersAggregatedState modifierState = new ModifiersAggregatedState();
+ DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder();
+ stateBuilder.setBrightness(currentBrightness);
+
+ int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC
+ : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER;
+
+ mModifier.applyStateChange(modifierState);
+ assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness);
+ assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason);
+
+ mModifier.apply(mMockRequest, stateBuilder);
+
+ assertThat(stateBuilder.getMaxBrightness())
+ .isWithin(BrightnessSynchronizer.EPSILON).of(maxBrightness);
+ assertThat(stateBuilder.getBrightness())
+ .isWithin(BrightnessSynchronizer.EPSILON).of(brightness);
+ assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason);
+ assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier);
+ assertThat(stateBuilder.getCustomAnimationRate()).isEqualTo(customAnimationRate);
+ }
+
+ private static class TestPmicMonitor extends PmicMonitor {
+ private Temperature mCurrentTemperature;
+ private float mCurrentAvgPower;
+
+ private final PowerChangeListener mListener;
+ TestPmicMonitor(PowerChangeListener listener,
+ IThermalService thermalService,
+ int pollingTimeMax, int pollingTimeMin) {
+ super(listener, thermalService, pollingTimeMax, pollingTimeMin);
+ mListener = listener;
+ }
+ public void setAvgPowerConsumed(float power) {
+ mCurrentAvgPower = power;
+ mListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+ }
+ public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+ mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+ mListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+ }
+ }
+
+ private class TestInjector extends BrightnessPowerModifier.Injector {
+ private TestPmicMonitor mCapturedPmicMonitor;
+ @NonNull
+ @Override
+ TestPmicMonitor getPmicMonitor(PowerChangeListener listener, IThermalService thermalService,
+ int minPollingTimeMillis, int maxPollingTimeMillis) {
+ mCapturedPmicMonitor = new TestPmicMonitor(listener, thermalService,
+ maxPollingTimeMillis, minPollingTimeMillis);
+ return mCapturedPmicMonitor;
+ }
+
+ @Override
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
index be4e7c7a9edd..7e4042ed2d05 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
@@ -44,6 +44,8 @@ public class DisplayDimModifierTest {
private static final float MIN_DIM_AMOUNT = 0.05f;
private static final float DIM_CONFIG = 0.4f;
+ private static final int DISPLAY_ID = 3;
+
@Mock
private Context mMockContext;
@@ -66,9 +68,9 @@ public class DisplayDimModifierTest {
R.dimen.config_screenBrightnessMinimumDimAmountFloat)).thenReturn(MIN_DIM_AMOUNT);
when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
when(mMockPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
+ DISPLAY_ID, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
- mModifier = new DisplayDimModifier(mMockContext);
+ mModifier = new DisplayDimModifier(DISPLAY_ID, mMockContext);
mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
index 99dfa739fb80..e3893c86a6de 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
@@ -129,7 +129,8 @@ public class AutoBrightnessFallbackStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mAutoBrightnessFallbackStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
@@ -149,7 +150,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ false,
- /* isAutoBrightnessEnabled= */ true);
+ /* isAutoBrightnessEnabled= */ true,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(true);
@@ -171,7 +173,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ false,
- /* isAutoBrightnessEnabled= */ true);
+ /* isAutoBrightnessEnabled= */ true,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(true);
@@ -193,7 +196,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ false,
- /* isAutoBrightnessEnabled= */ false);
+ /* isAutoBrightnessEnabled= */ false,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
@@ -215,7 +219,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ false,
- /* isAutoBrightnessEnabled= */ true);
+ /* isAutoBrightnessEnabled= */ true,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
@@ -237,7 +242,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ false,
- /* isAutoBrightnessEnabled= */ true);
+ /* isAutoBrightnessEnabled= */ true,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
@@ -259,7 +265,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ true,
- /* isAutoBrightnessEnabled= */ true);
+ /* isAutoBrightnessEnabled= */ true,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
@@ -281,7 +288,8 @@ public class AutoBrightnessFallbackStrategyTest {
/* lastUserSetScreenBrightness= */ PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userSetBrightnessChanged= */ false,
/* allowAutoBrightnessWhileDozingConfig= */ false,
- /* isAutoBrightnessEnabled= */ true);
+ /* isAutoBrightnessEnabled= */ true,
+ /* isBedtimeModeWearEnabled= */ false);
mAutoBrightnessFallbackStrategy.strategySelectionPostProcessor(ssnr);
verify(mScreenOffBrightnessSensorController).setLightSensorEnabled(false);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
index e38654225c29..ae1b01959ced 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
@@ -112,7 +112,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
mBrightnessConfiguration,
@@ -139,7 +140,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -167,7 +169,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
useNormalBrightnessForDoze,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -193,7 +196,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
mBrightnessConfiguration,
@@ -223,7 +227,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
useNormalBrightnessForDoze,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
mBrightnessConfiguration,
@@ -250,7 +255,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -278,7 +284,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
mBrightnessConfiguration,
@@ -307,7 +314,8 @@ public class AutomaticBrightnessStrategy2Test {
mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 50f814da6488..4be96c2a2eb3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Resources;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -76,6 +77,9 @@ public class AutomaticBrightnessStrategyTest {
@Mock
private DisplayManagerFlags mDisplayManagerFlags;
+ @Mock
+ private Resources mMockResources;
+
private BrightnessConfiguration mBrightnessConfiguration;
private float mDefaultScreenAutoBrightnessAdjustment;
private Context mContext;
@@ -123,7 +127,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
mBrightnessConfiguration,
@@ -150,7 +155,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -177,7 +183,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -204,7 +211,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
mBrightnessConfiguration,
@@ -233,7 +241,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
mBrightnessConfiguration,
@@ -261,7 +270,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, lastUserSetBrightness,
- userSetBrightnessChanged);
+ userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -290,7 +300,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
- lastUserSetBrightness, userSetBrightnessChanged);
+ lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
mBrightnessConfiguration,
@@ -319,7 +330,8 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, lastUserSetBrightness,
- userSetBrightnessChanged);
+ userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController)
.configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
mBrightnessConfiguration,
@@ -352,7 +364,8 @@ public class AutomaticBrightnessStrategyTest {
when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(true);
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController, never())
.switchMode(anyInt(), /* sendUpdate= */ anyBoolean());
@@ -361,21 +374,24 @@ public class AutomaticBrightnessStrategyTest {
when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false);
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
verify(mAutomaticBrightnessController).switchMode(
AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
/* sendUpdate= */ false);
reset(mAutomaticBrightnessController);
when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false);
- when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled(mContext)).thenReturn(
+ true);
policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
// Validate interaction when automaticBrightnessController is in non-idle mode, display
// state is DOZE, policy is DOZE and useNormalBrightnessForDoze is false.
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
// 1st AUTO_BRIGHTNESS_MODE_DOZE
verify(mAutomaticBrightnessController).switchMode(
AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE,
@@ -385,7 +401,8 @@ public class AutomaticBrightnessStrategyTest {
// state is ON, policy is DOZE and useNormalBrightnessForDoze is false.
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
// 2nd AUTO_BRIGHTNESS_MODE_DOZE
verify(mAutomaticBrightnessController, times(2)).switchMode(
AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE,
@@ -396,21 +413,53 @@ public class AutomaticBrightnessStrategyTest {
// state is DOZE, policy is DOZE and useNormalBrightnessForDoze is true.
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
- // 1st AUTO_BRIGHTNESS_MODE_DEFAULT
- verify(mAutomaticBrightnessController).switchMode(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
+ // 3rd AUTO_BRIGHTNESS_MODE_DOZE
+ verify(mAutomaticBrightnessController, times(3)).switchMode(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE,
/* sendUpdate= */ false);
// Validate interaction when automaticBrightnessController is in non-idle mode, display
// state is ON, policy is DOZE and useNormalBrightnessForDoze is true.
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
allowAutoBrightnessWhileDozing, brightnessReason, policy,
- useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
- // 2nd AUTO_BRIGHTNESS_MODE_DEFAULT
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ false);
+ // AUTO_BRIGHTNESS_MODE_DEFAULT
+ verify(mAutomaticBrightnessController).switchMode(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* sendUpdate= */ false);
+
+ // Wear Bedtime autobrightness mode feature disabled.
+ when(mDisplayManagerFlags.isAutoBrightnessModeBedtimeWearEnabled()).thenReturn(false);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy,
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ true);
verify(mAutomaticBrightnessController, times(2)).switchMode(
AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
/* sendUpdate= */ false);
+
+ // Wear Bedtime autobrightness mode feature enabled.
+ when(mDisplayManagerFlags.isAutoBrightnessModeBedtimeWearEnabled()).thenReturn(true);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy,
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ true);
+ verify(mAutomaticBrightnessController).switchMode(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ /* sendUpdate= */ false);
+
+ // Wear bedtime mode enabled, keep bedtime curve even though doze is requested.
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
+ allowAutoBrightnessWhileDozing, brightnessReason,
+ DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE,
+ useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged,
+ /* isBedtimeModeWearEnabled= */ true);
+ verify(mAutomaticBrightnessController, times(2)).switchMode(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR,
+ /* sendUpdate= */ false);
}
@Test
@@ -429,14 +478,14 @@ public class AutomaticBrightnessStrategyTest {
setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
lastUserSetScreenBrightness, policy, targetDisplayState,
- DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+ DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
brightnessConfiguration, autoBrightnessState);
verify(mAutomaticBrightnessController).configure(autoBrightnessState,
brightnessConfiguration,
lastUserSetScreenBrightness,
userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
/* userChangedAutoBrightnessAdjustment= */ false, policy, targetDisplayState,
- DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+ DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
/* shouldResetShortTermModel= */ true);
assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
@@ -447,7 +496,7 @@ public class AutomaticBrightnessStrategyTest {
mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
lastUserSetScreenBrightness, policy, targetDisplayState,
- DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+ DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
brightnessConfiguration, autoBrightnessState);
assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
@@ -572,7 +621,7 @@ public class AutomaticBrightnessStrategyTest {
BrightnessReason.REASON_UNKNOWN,
DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, /* lastUserSetScreenBrightness= */ 0.1f,
- /* userSetBrightnessChanged= */ false);
+ /* userSetBrightnessChanged= */ false, /* isBedtimeModeWearEnabled= */ false);
when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
.thenReturn(Float.NaN);
assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
@@ -589,7 +638,7 @@ public class AutomaticBrightnessStrategyTest {
BrightnessReason.REASON_UNKNOWN,
DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, /* lastUserSetScreenBrightness= */ 0.1f,
- /* userSetBrightnessChanged= */ false);
+ /* userSetBrightnessChanged= */ false, /* isBedtimeModeWearEnabled= */ false);
when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
.thenReturn(0.2f);
assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
@@ -637,7 +686,7 @@ public class AutomaticBrightnessStrategyTest {
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
.updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
- /* userSetBrightnessChanged= */ true));
+ /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
@@ -686,7 +735,7 @@ public class AutomaticBrightnessStrategyTest {
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
.updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
- /* userSetBrightnessChanged= */ true));
+ /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
@@ -725,7 +774,7 @@ public class AutomaticBrightnessStrategyTest {
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
.updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
- /* userSetBrightnessChanged= */ true));
+ /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
@@ -764,7 +813,7 @@ public class AutomaticBrightnessStrategyTest {
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
.updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
- /* userSetBrightnessChanged= */ true));
+ /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
index 275bb3efee8e..c03309e8b4a4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -60,7 +60,8 @@ public class BoostBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mBoostBrightnessStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
index 23e447c25245..e7f80b04e669 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
@@ -57,7 +57,8 @@ public class DozeBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mDozeBrightnessModeStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
index c4a579092d38..dcfa174a53f5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
@@ -61,7 +61,8 @@ public class FallbackBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mFallbackBrightnessStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, currentBrightness,
- /* userSetBrightnessChanged= */ true));
+ /* userSetBrightnessChanged= */ true,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
index c01f96e800de..239cdb6002e9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
@@ -61,7 +61,8 @@ public class FollowerBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mFollowerBrightnessStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
index 9fb2afa26ed2..77302f8747c1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
@@ -72,7 +72,8 @@ public class OffloadBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mOffloadBrightnessStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy
.getOffloadScreenBrightness(), 0.0f);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
index e8b4c06b9c89..414f274a9b58 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
@@ -16,8 +16,9 @@
package com.android.server.display.brightness.strategy;
+import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.hardware.display.DisplayManagerInternal;
@@ -34,7 +35,6 @@ import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
-
public class OverrideBrightnessStrategyTest {
private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
@@ -60,8 +60,58 @@ public class OverrideBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mOverrideBrightnessStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
- assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
+ assertThat(updatedDisplayBrightnessState).isEqualTo(expectedDisplayBrightnessState);
}
+ @Test
+ public void testUpdateBrightnessWhenWindowManagerOverrideIsRequested() {
+ var overrideRequest = new DisplayManagerInternal.DisplayBrightnessOverrideRequest();
+ overrideRequest.brightness = 0.2f;
+ mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(overrideRequest);
+ DisplayManagerInternal.DisplayPowerRequest
+ displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.screenBrightnessOverride = BRIGHTNESS_INVALID_FLOAT;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_OVERRIDE);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(overrideRequest.brightness)
+ .setBrightnessReason(brightnessReason)
+ .setDisplayBrightnessStrategyName(mOverrideBrightnessStrategy.getName())
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mOverrideBrightnessStrategy.updateBrightness(
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
+ assertThat(updatedDisplayBrightnessState).isEqualTo(expectedDisplayBrightnessState);
+ }
+
+ @Test
+ public void testUpdateWindowManagerBrightnessOverride() {
+ var request = new DisplayManagerInternal.DisplayBrightnessOverrideRequest();
+ assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(request))
+ .isFalse();
+ assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())
+ .isEqualTo(BRIGHTNESS_INVALID_FLOAT);
+
+ request.brightness = 0.2f;
+ assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(request))
+ .isTrue();
+ assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())
+ .isEqualTo(0.2f);
+
+ // Passing the same request doesn't result in an update.
+ assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(request))
+ .isFalse();
+ assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())
+ .isEqualTo(0.2f);
+
+ assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(null))
+ .isTrue();
+ assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())
+ .isEqualTo(BRIGHTNESS_INVALID_FLOAT);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
index 38709ece7007..652663e52a0a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
@@ -58,7 +58,8 @@ public final class ScreenOffBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mScreenOffBrightnessModeStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
index f523b6af426b..0022cab371e8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
@@ -60,7 +60,8 @@ public class TemporaryBrightnessStrategyTest {
DisplayBrightnessState updatedDisplayBrightnessState =
mTemporaryBrightnessStrategy.updateBrightness(
new StrategyExecutionRequest(displayPowerRequest, 0.2f,
- /* userSetBrightnessChanged= */ false));
+ /* userSetBrightnessChanged= */ false,
+ /* isStylusBeingUsed */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 3c77ec925078..3aef6aa2ee3f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -97,11 +97,14 @@ class BrightnessObserverTest {
private fun setUpLowBrightnessZone() {
whenever(mockInjector.getBrightnessInfo(Display.DEFAULT_DISPLAY)).thenReturn(
- BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f,
- /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- /* highBrightnessTransitionPoint = */ 1.0f,
- BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE))
+ BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f,
+ /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ /* highBrightnessTransitionPoint = */ 1.0f,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+ false /* isBrightnessOverrideByWindow */
+ )
+ )
whenever(mockDeviceConfig.highDisplayBrightnessThresholds).thenReturn(floatArrayOf())
whenever(mockDeviceConfig.highAmbientBrightnessThresholds).thenReturn(floatArrayOf())
whenever(mockDeviceConfig.lowDisplayBrightnessThresholds).thenReturn(floatArrayOf(0.1f))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index d91f154c1b87..4e0bab8bf4bd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1225,8 +1225,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
setBrightness(10, 10, displayListener);
@@ -1256,8 +1256,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
setBrightness(10, 10, displayListener);
@@ -1291,8 +1291,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
setBrightness(10, 10, displayListener);
@@ -1325,8 +1325,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
@@ -1404,8 +1404,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
@@ -1464,8 +1464,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
ArgumentCaptor<SensorEventListener> listenerCaptor =
@@ -1630,8 +1630,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
// Get the sensor listener so that we can give it new light sensor events
@@ -1730,8 +1730,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener displayListener = displayListenerCaptor.getValue();
// Get the sensor listener so that we can give it new light sensor events
@@ -1872,6 +1872,60 @@ public class DisplayModeDirectorTest {
}
@Test
+ public void testPeakRefreshRate_notAppliedToExternalDisplays() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ mInjector.mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ Vote vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertThat(vote1).isNull();
+ assertThat(vote2).isNull();
+
+ // Enable Smooth Display
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertThat(vote1).isNull();
+ assertThat(vote2).isNull();
+ }
+
+ @Test
public void testPeakRefreshRate_DisplayChanged() {
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
@@ -1968,8 +2022,9 @@ public class DisplayModeDirectorTest {
@Test
@Parameters({
"true, true, 60",
- "false, true, 50",
- "true, false, 50"
+ "false, true, 60",
+ "true, false, 50",
+ "false, false, 50"
})
public void testExternalDisplayMaxRefreshRate(boolean isRefreshRateSynchronizationEnabled,
boolean isExternalDisplay, float expectedMaxRenderFrameRate) {
@@ -2822,8 +2877,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> captor =
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener listener = captor.getValue();
// Specify Limitation
@@ -2945,8 +3000,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> captor =
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener listener = captor.getValue();
final int initialRefreshRate = 60;
@@ -3020,8 +3075,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> captor =
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener listener = captor.getValue();
// Specify Limitation for different display
@@ -3060,8 +3115,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> captor =
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener listener = captor.getValue();
// Specify Limitation
@@ -3145,8 +3200,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener listener = captor.getValue();
// Specify Sunlight limitations
@@ -3184,8 +3239,8 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> captor =
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+ eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
DisplayListener listener = captor.getValue();
// Specify Limitation for different display
@@ -3731,8 +3786,9 @@ public class DisplayModeDirectorTest {
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
- BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+ false /* isBrightnessOverrideByWindow */));
listener.onDisplayChanged(DISPLAY_ID);
}
@@ -3810,6 +3866,7 @@ public class DisplayModeDirectorTest {
SensorManagerInternal sensorManagerInternal) {
mDeviceConfig = new FakeDeviceConfig();
mDisplayInfo = new DisplayInfo();
+ mDisplayInfo.type = Display.TYPE_INTERNAL;
mDisplayInfo.defaultModeId = MODE_ID;
mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
800, 600, /* refreshRate= */ 60)};
@@ -3841,7 +3898,12 @@ public class DisplayModeDirectorTest {
public void registerDisplayListener(DisplayListener listener, Handler handler) {}
@Override
- public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
+ public void registerDisplayListener(DisplayListener listener, Handler handler,
+ long flags) {}
+
+ @Override
+ public void registerDisplayListener(DisplayListener listener, Handler handler, long flag,
+ long privateFlag) {}
@Override
public Display getDisplay(int displayId) {
@@ -3856,6 +3918,7 @@ public class DisplayModeDirectorTest {
@Override
public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.displayId = displayId;
return mDisplayInfoValid;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 5e240cf66674..e3f150e9fbc1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -189,6 +189,7 @@ public class DisplayObserverTest {
@Test
public void testExternalDisplay_voteUserPreferredMode() {
when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(false);
var preferredMode = mExternalDisplayModes[5];
mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
var expectedVote = Vote.forSize(
@@ -229,6 +230,108 @@ public class DisplayObserverTest {
.isEqualTo(null);
}
+ /** Vote for user preferred mode */
+ @Test
+ public void testDefaultDisplay_voteUserPreferredMode() {
+ when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true);
+ var preferredMode = mInternalDisplayModes[5];
+ mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ var expectedVote = Vote.forSize(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight());
+ init();
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+ mObserver.onDisplayAdded(DEFAULT_DISPLAY);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ mInternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ mObserver.onDisplayChanged(DEFAULT_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ preferredMode = mInternalDisplayModes[4];
+ mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ expectedVote = Vote.forSize(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight());
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ mObserver.onDisplayChanged(DEFAULT_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ // Testing that the vote is removed.
+ mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ }
+
+ /** Vote for user preferred mode with refresh rate, synchronization vote must be disabled */
+ @Test
+ public void testExternalDisplay_voteUserPreferredMode_withRefreshRate() {
+ when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(false);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true);
+ var preferredMode = mExternalDisplayModes[5];
+ mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ var expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate(),
+ preferredMode.getRefreshRate());
+ init();
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ mExternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ preferredMode = mExternalDisplayModes[4];
+ mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate(),
+ preferredMode.getRefreshRate());
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ // Testing that the vote is removed.
+ mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ }
+
/** External display: Do not apply limit to user preferred mode */
@Test
public void testExternalDisplay_doNotApplyLimitToUserPreferredMode() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
index b2d83d744ce6..9a93fba040cc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
@@ -43,35 +43,42 @@ class SyntheticModeManagerTest {
@Test
fun testAppSupportedModes(@TestParameter testCase: AppSupportedModesTestCase) {
whenever(mockFlags.isSynthetic60HzModesEnabled).thenReturn(testCase.syntheticModesEnabled)
+ whenever(mockFlags.hasArrSupportFlag()).thenReturn(testCase.hasArrSupport)
whenever(mockConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
val syntheticModeManager = SyntheticModeManager(mockFlags)
val result = syntheticModeManager.createAppSupportedModes(
- mockConfig, testCase.supportedModes)
+ mockConfig, testCase.supportedModes, testCase.hasArrSupport)
assertThat(result).isEqualTo(testCase.expectedAppModes)
}
+ // TODO(b/361433651) Remove vrrSupported once hasArrSupport is rolled out
enum class AppSupportedModesTestCase(
val syntheticModesEnabled: Boolean,
val vrrSupported: Boolean,
+ val hasArrSupport: Boolean,
val supportedModes: Array<Mode>,
val expectedAppModes: Array<Mode>
) {
- SYNTHETIC_MODES_NOT_SUPPORTED(false, true, DISPLAY_MODES, DISPLAY_MODES),
- VRR_NOT_SUPPORTED(true, false, DISPLAY_MODES, DISPLAY_MODES),
- VRR_SYNTHETIC_NOT_SUPPORTED(false, false, DISPLAY_MODES, DISPLAY_MODES),
- SINGLE_RESOLUTION_MODES(true, true, DISPLAY_MODES, arrayOf(
+ SYNTHETIC_MODES_NOT_SUPPORTED(false, true, true, DISPLAY_MODES, DISPLAY_MODES),
+ VRR_NOT_SUPPORTED(true, false, false, DISPLAY_MODES, DISPLAY_MODES),
+ VRR_SYNTHETIC_NOT_SUPPORTED(false, false, false, DISPLAY_MODES, DISPLAY_MODES),
+ SINGLE_RESOLUTION_MODES(true, true, true, DISPLAY_MODES, arrayOf(
Mode(2, 100, 100, 120f),
Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf())
)),
- NO_60HZ_MODES(true, true, arrayOf(Mode(2, 100, 100, 120f)),
+ SINGLE_RESOLUTION_MODES_HASARR(true, false, true, DISPLAY_MODES, arrayOf(
+ Mode(2, 100, 100, 120f),
+ Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf())
+ )),
+ NO_60HZ_MODES(true, true, true, arrayOf(Mode(2, 100, 100, 120f)),
arrayOf(
Mode(2, 100, 100, 120f),
Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf())
)
),
- MULTI_RESOLUTION_MODES(true, true,
+ MULTI_RESOLUTION_MODES(true, true, true,
arrayOf(
Mode(1, 100, 100, 120f),
Mode(2, 200, 200, 60f),
@@ -86,7 +93,7 @@ class SyntheticModeManagerTest {
Mode(7, 300, 300, 60f, 60f, true, floatArrayOf(), intArrayOf())
)
),
- WITH_HDR_TYPES(true, true,
+ WITH_HDR_TYPES(true, true, true,
arrayOf(
Mode(1, 100, 100, 120f, 120f, false, floatArrayOf(), intArrayOf(1, 2)),
Mode(2, 200, 200, 60f, 120f, false, floatArrayOf(), intArrayOf(3, 4)),
@@ -99,7 +106,7 @@ class SyntheticModeManagerTest {
Mode(5, 200, 200, 60f, 60f, true, floatArrayOf(), intArrayOf(5, 6)),
)
),
- UNACHIEVABLE_60HZ(true, true,
+ UNACHIEVABLE_60HZ(true, true, true,
arrayOf(
Mode(1, 100, 100, 90f),
),
@@ -107,7 +114,7 @@ class SyntheticModeManagerTest {
Mode(1, 100, 100, 90f),
)
),
- MULTI_RESOLUTION_MODES_WITH_UNACHIEVABLE_60HZ(true, true,
+ MULTI_RESOLUTION_MODES_WITH_UNACHIEVABLE_60HZ(true, true, true,
arrayOf(
Mode(1, 100, 100, 120f),
Mode(2, 200, 200, 90f),
@@ -118,7 +125,7 @@ class SyntheticModeManagerTest {
Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf()),
)
),
- LOWER_THAN_60HZ_MODES(true, true,
+ LOWER_THAN_60HZ_MODES(true, true, true,
arrayOf(
Mode(1, 100, 100, 30f),
Mode(2, 100, 100, 45f),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
index 9ea7ea7ef23d..56e4048c842e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -149,6 +150,29 @@ class SystemRequestObserverTest {
}
@Test
+ fun testTokenUnlinkToDeath_noVotes() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+ verify(mockToken, never()).unlinkToDeath(any(), eq(0))
+ }
+
+ @Test
+ fun testTokenUnlinkToDeath_removedVotes() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+ clearInvocations(mockToken)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+ verify(mockToken, never()).unlinkToDeath(any(), eq(0))
+ }
+
+ @Test
fun testTokenUnlinkToDeathNotCalled_votesForOtherDisplayInStorage() {
val systemRequestObserver = SystemRequestObserver(storage)
val requestedModes = intArrayOf(1, 2, 3)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
new file mode 100644
index 000000000000..01061f16c279
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.display.plugin.PluginManager.PluginChangeListener
+
+import org.junit.Test
+
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private val TEST_PLUGIN_TYPE = PluginType(Int::class.java, "test_type")
+
+@SmallTest
+class PluginManagerTest {
+
+ private val mockContext = mock<Context>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockListener = mock<PluginChangeListener<Int>>()
+ private val testInjector = TestInjector()
+
+ @Test
+ fun testBootCompleted_enabledPluginManager() {
+ val pluginManager = createPluginManager()
+
+ pluginManager.onBootCompleted()
+
+ verify(testInjector.mockPlugin1).onBootCompleted()
+ verify(testInjector.mockPlugin2).onBootCompleted()
+ }
+
+ @Test
+ fun testBootCompleted_disabledPluginManager() {
+ val pluginManager = createPluginManager(false)
+
+ pluginManager.onBootCompleted()
+
+ verify(testInjector.mockPlugin1, never()).onBootCompleted()
+ verify(testInjector.mockPlugin2, never()).onBootCompleted()
+ }
+
+ @Test
+ fun testSubscribe() {
+ val pluginManager = createPluginManager()
+
+ pluginManager.subscribe(TEST_PLUGIN_TYPE, mockListener)
+
+ verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, mockListener)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ val pluginManager = createPluginManager()
+
+ pluginManager.unsubscribe(TEST_PLUGIN_TYPE, mockListener)
+
+ verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, mockListener)
+ }
+
+ private fun createPluginManager(enabled: Boolean = true): PluginManager {
+ whenever(mockFlags.isPluginManagerEnabled).thenReturn(enabled)
+ return PluginManager(mockContext, mockFlags, testInjector)
+ }
+
+ private class TestInjector : PluginManager.Injector() {
+ val mockStorage = mock<PluginStorage>()
+ val mockPlugin1 = mock<Plugin>()
+ val mockPlugin2 = mock<Plugin>()
+
+ override fun getPluginStorage(): PluginStorage {
+ return mockStorage
+ }
+
+ override fun loadPlugins(context: Context?, storage: PluginStorage?): List<Plugin> {
+ return listOf(mockPlugin1, mockPlugin2)
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
new file mode 100644
index 000000000000..218e34134e93
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.plugin
+
+import androidx.test.filters.SmallTest
+import com.android.server.display.plugin.PluginManager.PluginChangeListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+private val TEST_PLUGIN_TYPE1 = PluginType(String::class.java, "test_type1")
+private val TEST_PLUGIN_TYPE2 = PluginType(String::class.java, "test_type2")
+
+@SmallTest
+class PluginStorageTest {
+
+ val storage = PluginStorage()
+
+ @Test
+ fun testUpdateValue() {
+ val type1Value = "value1"
+ val testChangeListener = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+
+ assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
+ }
+
+ @Test
+ fun testAddListener() {
+ val type1Value = "value1"
+ val testChangeListener = TestPluginChangeListener<String>()
+ storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+
+ storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+
+ assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
+ }
+
+ @Test
+ fun testRemoveListener() {
+ val type1Value = "value1"
+ val testChangeListener = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.removeListener(TEST_PLUGIN_TYPE1, testChangeListener)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+
+ assertThat(testChangeListener.receivedValue).isNull()
+ }
+
+ @Test
+ fun testAddListener_multipleValues() {
+ val type1Value = "value1"
+ val type2Value = "value2"
+ val testChangeListener = TestPluginChangeListener<String>()
+ storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE2, type2Value)
+
+ storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+
+ assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
+ }
+
+ @Test
+ fun testUpdateValue_multipleListeners() {
+ val type1Value = "value1"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE2, testChangeListener2)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
+ assertThat(testChangeListener2.receivedValue).isNull()
+ }
+
+ private class TestPluginChangeListener<T> : PluginChangeListener<T> {
+ var receivedValue: T? = null
+
+ override fun onChanged(value: T?) {
+ receivedValue = value
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
index de53266dc14e..fc4cc25243c1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -63,11 +63,12 @@ public final class DisplayStateControllerTest {
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
assertTrue(Display.STATE_OFF == stateAndReason.first);
- assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+ assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_OFF);
assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -105,11 +106,12 @@ public final class DisplayStateControllerTest {
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
assertTrue(Display.STATE_OFF == stateAndReason.first);
- assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+ assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -123,11 +125,12 @@ public final class DisplayStateControllerTest {
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, DISPLAY_ENABLED, DISPLAY_IN_TRANSITION);
assertTrue(Display.STATE_OFF == stateAndReason.first);
- assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+ assertTrue(Display.STATE_REASON_MOTION == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -141,6 +144,7 @@ public final class DisplayStateControllerTest {
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
@@ -156,6 +160,7 @@ public final class DisplayStateControllerTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
new DisplayManagerInternal.DisplayPowerRequest();
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DRAW_WAKE_LOCK;
mDisplayStateController.overrideDozeScreenState(
Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_OFFLOAD);
@@ -172,8 +177,9 @@ public final class DisplayStateControllerTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
new DisplayManagerInternal.DisplayPowerRequest();
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
mDisplayStateController.overrideDozeScreenState(
- Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DEFAULT_POLICY);
+ Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK);
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
@@ -183,6 +189,53 @@ public final class DisplayStateControllerTest {
assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
}
+ @Test
+ public void policyOff_usespolicyReasonFromRequest() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
+
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
+ }
+
+ @Test
+ public void policyBright_usespolicyReasonFromRequest() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DREAM_MANAGER;
+
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+ assertTrue(Display.STATE_ON == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DREAM_MANAGER == stateAndReason.second);
+ }
+
+ @Test
+ public void policyRequestHasDozeScreenState_usesPolicyDozeScreenStateReason() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
+ displayPowerRequest.dozeScreenState = Display.STATE_ON;
+ displayPowerRequest.dozeScreenStateReason = Display.STATE_REASON_OFFLOAD;
+
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+ assertTrue(Display.STATE_ON == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_OFFLOAD == stateAndReason.second);
+ }
+
private void validDisplayState(int policy, int displayState, boolean isEnabled,
boolean isInTransition) {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
index 121145672d68..439243e85e75 100644
--- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
@@ -82,6 +82,11 @@ public class AudioManagerRouteControllerTest {
private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET =
createAudioDeviceInfo(
AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+ "name_wired_hs_with_address",
+ /* address= */ "card=1;device=0");
private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP =
createAudioDeviceInfo(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45");
@@ -304,6 +309,55 @@ public class AudioManagerRouteControllerTest {
assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME);
}
+ @Test
+ public void getAvailableRoutes_whenAddressIsPopulatedForNonBluetoothDevice_usesCorrectName() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS,
+ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+
+ List<MediaRoute2Info> availableRoutes = mControllerUnderTest.getAvailableRoutes();
+ assertThat(availableRoutes.size()).isEqualTo(3);
+
+ assertThat(
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET)
+ .getName()
+ .toString())
+ .isEqualTo(
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS
+ .getProductName()
+ .toString());
+
+ assertThat(
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
+ .getName()
+ .toString())
+ .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP.getProductName().toString());
+ }
+
+ @Test
+ public void
+ getAvailableRoutes_whenAddressIsNotPopulatedForNonBluetoothDevice_usesCorrectName() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+
+ List<MediaRoute2Info> availableRoutes = mControllerUnderTest.getAvailableRoutes();
+ assertThat(availableRoutes.size()).isEqualTo(2);
+
+ assertThat(
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)
+ .getName()
+ .toString())
+ .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString());
+
+ assertThat(
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET)
+ .getName()
+ .toString())
+ .isEqualTo(FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET.getProductName().toString());
+ }
+
// Internal methods.
@NonNull
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index c81d6be43223..0d25426700a6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
java_defaults {
- name: "FrameworkMockingServicesTests-jni-defaults",
+ name: "FrameworksMockingServicesTests-jni-defaults",
jni_libs: [
"libmockingservicestestjni",
],
@@ -30,7 +30,7 @@ package {
android_test {
name: "FrameworksMockingServicesTests",
defaults: [
- "FrameworkMockingServicesTests-jni-defaults",
+ "FrameworksMockingServicesTests-jni-defaults",
"modules-utils-testable-device-config-defaults",
],
@@ -77,7 +77,10 @@ android_test {
"flag-junit",
"am_flags_lib",
"device_policy_aconfig_flags_lib",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": ["service-crashrecovery-pre-jarjar"],
+ default: [],
+ }),
libs: [
"android.test.mock.stubs.system",
@@ -106,6 +109,10 @@ android_test {
optimize: {
enabled: false,
},
+
+ data: [
+ ":HelloWorldUsingSdk1And2",
+ ],
}
java_library {
@@ -122,22 +129,6 @@ java_library {
],
}
-android_ravenwood_test {
- name: "FrameworksMockingServicesTestsRavenwood",
- libs: [
- "android.test.mock.stubs.system",
- ],
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.test.rules",
- "services.core",
- ],
- srcs: [
- "src/com/android/server/am/BroadcastRecordTest.java",
- ],
- auto_gen_config: true,
-}
-
test_module_config {
name: "FrameworksMockingServicesTests_blob",
base: "FrameworksMockingServicesTests",
@@ -401,3 +392,10 @@ test_module_config {
],
include_filters: ["com.android.server.StorageManagerServiceTest"],
}
+
+test_module_config {
+ name: "FrameworksMockingServicesTests_service_batteryServiceTest",
+ base: "FrameworksMockingServicesTests",
+ test_suites: ["device-tests"],
+ include_filters: ["com.android.server.BatteryServiceTest"],
+}
diff --git a/services/tests/mockingservicestests/AndroidTest.xml b/services/tests/mockingservicestests/AndroidTest.xml
index 7782d570856f..2b90119145bd 100644
--- a/services/tests/mockingservicestests/AndroidTest.xml
+++ b/services/tests/mockingservicestests/AndroidTest.xml
@@ -23,6 +23,12 @@
<option name="test-file-name" value="FrameworksMockingServicesTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true"/>
+ <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+ value="/data/local/tmp/tests/smockingservicestest/pm/HelloWorldUsingSdk1And2.apk"/>
+ </target_preparer>
+
<option name="test-tag" value="FrameworksMockingServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
new file mode 100644
index 000000000000..1fbd53a27a4f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.hardware.health.HealthInfo;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.flags.Flags;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryServiceTest {
+
+ private static final int CURRENT_BATTERY_VOLTAGE = 3000;
+ private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029;
+ private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030;
+ private static final int CURRENT_BATTERY_TEMP = 300;
+ private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305;
+ private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310;
+ private static final int CURRENT_BATTERY_HEALTH = 2;
+ private static final int UPDATED_BATTERY_HEALTH = 3;
+ private static final int CURRENT_CHARGE_COUNTER = 4680000;
+ private static final int UPDATED_CHARGE_COUNTER = 4218000;
+ private static final int CURRENT_MAX_CHARGING_CURRENT = 298125;
+ private static final int UPDATED_MAX_CHARGING_CURRENT = 398125;
+ private static final int HANDLER_IDLE_TIME_MS = 5000;
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(ActivityManager.class)
+ .mockStatic(BatteryStatsService.class)
+ .build();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private LightsManager mLightsManagerMock;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternalMock;
+ @Mock
+ private IBatteryStats mIBatteryStatsMock;
+
+ private BatteryService mBatteryService;
+ private String mSystemUiPackage;
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getResources().getString(R.string.config_systemUi);
+
+ when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
+ when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true);
+ when(mContextMock.getResources()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
+ ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock);
+
+ doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(),
+ anyInt(), anyInt(), anyInt(), anyInt(), anyLong());
+ doNothing().when(() -> SystemProperties.set(anyString(), anyString()));
+ doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(),
+ eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+ eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)));
+
+ addLocalServiceMock(LightsManager.class, mLightsManagerMock);
+ addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+
+ createBatteryService();
+ }
+
+ @Test
+ public void createBatteryService_withNullLooper_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
+ mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void voltageUpdated_withUpdateInChargingCurrent_broadcastSent() {
+ mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
+ mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void tempUpdated_broadcastSent() {
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
+ UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void batteryHealthUpdated_withOtherExtrasConstant_broadcastSent() {
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ UPDATED_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+
+ // updating counter just after the health update does not triggers broadcast.
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ UPDATED_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
+ mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyChargeCounterUpdated_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyChargeCounterUpdated_broadcastSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyMaxChargingCurrentUpdated_beforeFiveSeconds_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void maxChargingCurrentUpdated_afterFiveSeconds_broadcastSent() {
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime =
+ SystemClock.elapsedRealtime() - 5000;
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ private HealthInfo createHealthInfo(
+ int batteryVoltage,
+ int batteryTemperature,
+ int batteryChargeCounter,
+ int batteryHealth,
+ int maxChargingCurrent) {
+ HealthInfo h = new HealthInfo();
+ h.batteryVoltageMillivolts = batteryVoltage;
+ h.batteryTemperatureTenthsCelsius = batteryTemperature;
+ h.batteryChargeCounterUah = batteryChargeCounter;
+ h.batteryStatus = 5;
+ h.batteryHealth = batteryHealth;
+ h.batteryPresent = true;
+ h.batteryLevel = 100;
+ h.maxChargingCurrentMicroamps = maxChargingCurrent;
+ h.batteryCurrentAverageMicroamps = -2812;
+ h.batteryCurrentMicroamps = 298125;
+ h.maxChargingVoltageMicrovolts = 3000;
+ h.batteryCycleCount = 50;
+ h.chargingState = 4;
+ h.batteryCapacityLevel = 100;
+ return h;
+ }
+
+ // Creates a new battery service objects and sets the initial values.
+ private void createBatteryService() throws InterruptedException {
+ final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest");
+ handlerThread.start();
+
+ mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper());
+
+ // trigger the update to set the initial values.
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+ }
+
+ private void waitForHandlerToExecute() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mBatteryService.getHandlerForTest().post(latch::countDown);
+ boolean isExecutionComplete = false;
+
+ try {
+ isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Handler interrupted before executing the message " + e);
+ }
+
+ assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete);
+ }
+
+ private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) {
+ // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test
+ // setUp.
+ verify(() -> ActivityManager.broadcastStickyIntent(any(),
+ eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+ eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)),
+ times(++numberOfTimes));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index cbc8538cf9fb..37d1c30f76f5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -15,7 +15,12 @@
*/
package com.android.server;
+import static android.os.PowerExemptionManager.REASON_OTHER;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
+
import static androidx.test.InstrumentationRegistry.getContext;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -31,6 +36,7 @@ import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
+import static com.android.server.DeviceIdleController.MSG_TEMP_APP_WHITELIST_TIMEOUT;
import static com.android.server.DeviceIdleController.STATE_ACTIVE;
import static com.android.server.DeviceIdleController.STATE_IDLE;
import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE;
@@ -41,6 +47,7 @@ import static com.android.server.DeviceIdleController.STATE_QUICK_DOZE_DELAY;
import static com.android.server.DeviceIdleController.STATE_SENSING;
import static com.android.server.DeviceIdleController.lightStateToString;
import static com.android.server.DeviceIdleController.stateToString;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -83,6 +90,8 @@ import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.SystemClock;
import android.os.WearModeManagerInternal;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
@@ -90,12 +99,16 @@ import android.telephony.emergency.EmergencyNumber;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.am.BatteryStatsService;
import com.android.server.deviceidle.ConstraintController;
+import com.android.server.deviceidle.Flags;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -115,6 +128,9 @@ import java.util.concurrent.Executor;
@SuppressWarnings("GuardedBy")
@RunWith(AndroidJUnit4.class)
public class DeviceIdleControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT);
+
private DeviceIdleController mDeviceIdleController;
private DeviceIdleController.MyHandler mHandler;
private AnyMotionDetectorForTest mAnyMotionDetector;
@@ -157,7 +173,8 @@ public class DeviceIdleControllerTest {
LocationManager locationManager;
ConstraintController constraintController;
// Freeze time for testing.
- long nowElapsed;
+ volatile long nowElapsed;
+ volatile long nowUptime;
boolean useMotionSensor = true;
boolean isLocationPrefetchEnabled = true;
@@ -193,6 +210,11 @@ public class DeviceIdleControllerTest {
}
@Override
+ long getUptimeMillis() {
+ return nowUptime;
+ }
+
+ @Override
LocationManager getLocationManager() {
return locationManager;
}
@@ -314,11 +336,13 @@ public class DeviceIdleControllerTest {
mMockingSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .mockStatic(BatteryStatsService.class)
.spyStatic(DeviceConfig.class)
.spyStatic(LocalServices.class)
.startMocking();
spyOn(getContext());
doReturn(null).when(getContext()).registerReceiver(any(), any());
+ doReturn(mock(IBatteryStats.class)).when(() -> BatteryStatsService.getService());
doReturn(mock(ActivityManagerInternal.class))
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(ActivityTaskManagerInternal.class))
@@ -401,6 +425,46 @@ public class DeviceIdleControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST)
+ public void testTempAllowlistCountsUptime() {
+ doNothing().when(getContext()).sendBroadcastAsUser(any(), any(), any(), any());
+ final int testUid = 12345;
+ final long durationMs = 4300;
+ final long startTime = 100; // Arbitrary starting point in time.
+ mInjector.nowUptime = mInjector.nowElapsed = startTime;
+
+ mDeviceIdleController.addPowerSaveTempWhitelistAppDirectInternal(0, testUid, durationMs,
+ TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, true, REASON_OTHER, "test");
+
+ assertEquals(startTime + durationMs,
+ mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value);
+
+ final InOrder inorder = inOrder(mHandler);
+ // mHandler is already stubbed to do nothing on handleMessage.
+ inorder.verify(mHandler).sendMessageDelayed(
+ argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid),
+ eq(durationMs));
+
+ mInjector.nowElapsed += durationMs;
+ mInjector.nowUptime += 2;
+ // Elapsed time moved past the expiration but not uptime. The check should be rescheduled.
+ mDeviceIdleController.checkTempAppWhitelistTimeout(testUid);
+ inorder.verify(mHandler).sendMessageDelayed(
+ argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid),
+ eq(durationMs - 2));
+ assertEquals(startTime + durationMs,
+ mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value);
+
+ mInjector.nowUptime += durationMs;
+ // Uptime moved past the expiration time. Uid should be removed from the temp allowlist.
+ mDeviceIdleController.checkTempAppWhitelistTimeout(testUid);
+ inorder.verify(mHandler, never()).sendMessageDelayed(
+ argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid),
+ anyLong());
+ assertFalse(mDeviceIdleController.mTempWhitelistAppIdEndTimes.contains(testUid));
+ }
+
+ @Test
public void testUpdateInteractivityLocked() {
doReturn(false).when(mPowerManager).isInteractive();
mDeviceIdleController.updateInteractivityLocked();
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index f2acbc31b008..f40d8038da3b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -45,13 +45,11 @@ import android.crashrecovery.flags.Flags;
import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.util.ArraySet;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -74,11 +72,9 @@ import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.lang.reflect.Field;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -250,37 +246,6 @@ public class RescuePartyTest {
}
@Test
- @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
- Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
- public void testBootLoop() {
- // this is old test where the flag needs to be disabled
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
- HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
- noteBoot(1);
-
- // Record DeviceConfig accesses
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
- final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
- noteBoot(2);
- noteBoot(3);
-
- noteBoot(4);
- assertTrue(RescueParty.isRebootPropertySet());
-
- setCrashRecoveryPropAttemptingReboot(false);
- noteBoot(5);
- assertTrue(RescueParty.isFactoryResetPropertySet());
- }
- @Test
@EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testBootLoopNoFlags() {
// this is old test where the flag needs to be disabled
@@ -293,61 +258,6 @@ public class RescuePartyTest {
}
@Test
- @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testBootLoopRecoverability() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
- HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
- // Record DeviceConfig accesses
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
- final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
-
- noteBoot(1);
-
- noteBoot(2);
- assertTrue(RescueParty.isRebootPropertySet());
-
- noteBoot(3);
-
- verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-
- noteBoot(4);
- verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
-
- noteBoot(5);
- verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
-
- setCrashRecoveryPropAttemptingReboot(false);
- noteBoot(6);
- assertTrue(RescueParty.isFactoryResetPropertySet());
- }
-
- @Test
- @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
- Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
- public void testPersistentAppCrash() {
- // this is old test where the flag needs to be disabled
- noteAppCrash(1, true);
- noteAppCrash(2, true);
- noteAppCrash(3, true);
-
- noteAppCrash(4, true);
- assertTrue(RescueParty.isRebootPropertySet());
-
- setCrashRecoveryPropAttemptingReboot(false);
- noteAppCrash(5, true);
- assertTrue(RescueParty.isFactoryResetPropertySet());
- }
-
- @Test
@EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testPersistentAppCrashNoFlags() {
// this is old test where the flag needs to be disabled
@@ -360,98 +270,6 @@ public class RescuePartyTest {
}
@Test
- @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testPersistentAppCrashRecoverability() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
- HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
- // Record DeviceConfig accesses
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(PERSISTENT_PACKAGE, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
- final String[] expectedResetNamespaces = new String[]{NAMESPACE1};
- final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
- noteAppCrash(1, true);
-
- noteAppCrash(2, true);
-
- noteAppCrash(3, true);
- assertTrue(RescueParty.isRebootPropertySet());
-
- noteAppCrash(4, true);
- verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-
- noteAppCrash(5, true);
- verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
-
- noteAppCrash(6, true);
- verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
-
- setCrashRecoveryPropAttemptingReboot(false);
- noteAppCrash(7, true);
- assertTrue(RescueParty.isFactoryResetPropertySet());
- }
-
- @Test
- @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
- Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
- public void testNonPersistentApp() {
- // this is old test where the flag needs to be disabled
- noteAppCrash(1, false);
- noteAppCrash(2, false);
- noteAppCrash(3, false);
- assertFalse(RescueParty.isRebootPropertySet());
-
- noteAppCrash(5, false);
- assertFalse(RescueParty.isFactoryResetPropertySet());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testNonPersistentAppOnlyPerformsFlagResetsRecoverabilityDetection() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
- HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
- // Record DeviceConfig accesses
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(NON_PERSISTENT_PACKAGE, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
- final String[] expectedResetNamespaces = new String[]{NAMESPACE1};
- final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
- noteAppCrash(1, false);
-
- noteAppCrash(2, false);
-
- noteAppCrash(3, false);
- assertFalse(RescueParty.isRebootPropertySet());
-
- noteAppCrash(4, false);
- verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
- noteAppCrash(5, false);
- verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
- noteAppCrash(6, false);
- verifyNoSettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
-
- setCrashRecoveryPropAttemptingReboot(false);
- noteAppCrash(7, false);
- assertFalse(RescueParty.isFactoryResetPropertySet());
- }
-
- @Test
public void testIsRecoveryTriggeredReboot() {
for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
noteBoot(i + 1);
@@ -522,6 +340,7 @@ public class RescuePartyTest {
@Test
public void testNotThrottlingAfterTimeoutOnAppCrash() {
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
setCrashRecoveryPropAttemptingReboot(false);
long now = System.currentTimeMillis();
long afterTimeout = now - TimeUnit.MINUTES.toMillis(
@@ -534,30 +353,15 @@ public class RescuePartyTest {
}
@Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testNativeRescuePartyResets() {
- doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
- doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
- () -> SettingsToPropertiesMapper.getResetNativeCategories());
-
- RescueParty.onSettingsProviderPublished(mMockContext);
-
- verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
- FAKE_NATIVE_NAMESPACE1));
- verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
- FAKE_NATIVE_NAMESPACE2));
- }
-
- @Test
public void testExplicitlyEnablingAndDisablingRescue() {
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
- assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
- assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
+ assertTrue(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
}
@Test
@@ -565,8 +369,8 @@ public class RescuePartyTest {
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
- assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
// Restore the property value initialized in SetUp()
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
@@ -587,75 +391,6 @@ public class RescuePartyTest {
}
@Test
- @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
- Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
- public void testHealthCheckLevels() {
- // this is old test where the flag needs to be disabled
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
- // Ensure that no action is taken for cases where the failure reason is unknown
- assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
-
- // Ensure the correct user impact is returned for each mitigation count.
- assertEquals(observer.onHealthCheckFailed(null,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
- assertEquals(observer.onHealthCheckFailed(null,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
- assertEquals(observer.onHealthCheckFailed(null,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
-
- assertEquals(observer.onHealthCheckFailed(null,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
- public void testHealthCheckLevelsRecoverabilityDetection() {
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
- // Ensure that no action is taken for cases where the failure reason is unknown
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
-
- // Ensure the correct user impact is returned for each mitigation count.
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 6),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
- assertEquals(observer.onHealthCheckFailed(sFailingPackage,
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 7),
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
- }
- @Test
@EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testHealthCheckLevelsNoFlags() {
// this is old test where the flag needs to be disabled
@@ -674,36 +409,6 @@ public class RescuePartyTest {
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
}
- @Test
- @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
- Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
- public void testBootLoopLevels() {
- // this is old test where the flag needs to be disabled
-
-
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
- assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
- assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
- assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
- assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
- assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
- assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
- public void testBootLoopLevelsRecoverabilityDetection() {
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
- assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
- assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
- assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_71);
- assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_75);
- assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
- assertEquals(observer.onBootLoop(6), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- }
@Test
@EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
@@ -714,129 +419,6 @@ public class RescuePartyTest {
assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
}
- @Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
-
- // Record DeviceConfig accesses
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
- // Fake DeviceConfig value changes
- monitorCallback.onNamespaceUpdate(NAMESPACE1);
- monitorCallback.onNamespaceUpdate(NAMESPACE2);
- monitorCallback.onNamespaceUpdate(NAMESPACE3);
-
- doReturn("").when(() -> DeviceConfig.getString(
- eq(RescueParty.NAMESPACE_CONFIGURATION),
- eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
- eq("")));
-
- RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
- ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
- Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2}));
- assertEquals(mNamespacesWiped, expectedNamespacesWiped);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testResetDeviceConfigForPackagesOnlyPresetMap() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
-
- String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
- + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
- + NAMESPACE3 + ":" + CALLING_PACKAGE1;
- doReturn(presetMapping).when(() -> DeviceConfig.getString(
- eq(RescueParty.NAMESPACE_CONFIGURATION),
- eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
- eq("")));
-
- RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
- ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
- Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
- assertEquals(mNamespacesWiped, expectedNamespacesWiped);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testResetDeviceConfigForPackagesBothMaps() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
-
- // Record DeviceConfig accesses
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
- // Fake DeviceConfig value changes
- monitorCallback.onNamespaceUpdate(NAMESPACE1);
- monitorCallback.onNamespaceUpdate(NAMESPACE2);
- monitorCallback.onNamespaceUpdate(NAMESPACE3);
- monitorCallback.onNamespaceUpdate(NAMESPACE4);
-
- String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
- + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
- + NAMESPACE4 + ":" + CALLING_PACKAGE3;
- doReturn(presetMapping).when(() -> DeviceConfig.getString(
- eq(RescueParty.NAMESPACE_CONFIGURATION),
- eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
- eq("")));
-
- RescueParty.resetDeviceConfigForPackages(
- Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
- ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
- Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3}));
- assertEquals(mNamespacesWiped, expectedNamespacesWiped);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
- RescueParty.onSettingsProviderPublished(mMockContext);
- verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
- any(Executor.class),
- mMonitorCallbackCaptor.capture()));
-
- // Record DeviceConfig accesses
- RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
- DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
- monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
- // Fake DeviceConfig value changes
- monitorCallback.onNamespaceUpdate(NAMESPACE1);
- monitorCallback.onNamespaceUpdate(NAMESPACE2);
- monitorCallback.onNamespaceUpdate(NAMESPACE3);
- monitorCallback.onNamespaceUpdate(NAMESPACE4);
-
- String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
- + NAMESPACE1 + "." + CALLING_PACKAGE2;
- doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString(
- eq(RescueParty.NAMESPACE_CONFIGURATION),
- eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
- eq("")));
-
- RescueParty.resetDeviceConfigForPackages(
- Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
- ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
- Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
- assertEquals(mNamespacesWiped, expectedNamespacesWiped);
- }
private void verifySettingsResets(int resetMode, String[] resetNamespaces,
HashMap<String, Integer> configResetVerifiedTimesMap) {
@@ -858,13 +440,14 @@ public class RescuePartyTest {
}
private void noteBoot(int mitigationCount) {
- RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
+ RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount);
}
private void noteAppCrash(int mitigationCount, boolean isPersistent) {
String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
- RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
- packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
+ RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ new VersionedPackage(packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH,
+ mitigationCount);
}
// Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 30de0e8c7981..8dc8c14f8948 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -67,7 +67,6 @@ import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TEMPORARY_QUOTA_CHANGED;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
@@ -152,7 +151,6 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.flag.util.FlagSetException;
@@ -436,8 +434,7 @@ public final class AlarmManagerServiceTest {
*/
private void disableFlagsNotSetByAnnotation() {
try {
- mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
- Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
} catch (FlagSetException fse) {
// Expected if the test about to be run requires this enabled.
}
@@ -523,13 +520,11 @@ public final class AlarmManagerServiceTest {
mService.onStart();
- if (Flags.useFrozenStateToDropListenerAlarms()) {
- final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
- ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
- verify(mActivityManager).registerUidFrozenStateChangedCallback(
- any(HandlerExecutor.class), frozenCaptor.capture());
- mUidFrozenStateCallback = frozenCaptor.getValue();
- }
+ final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
+ ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
+ verify(mActivityManager).registerUidFrozenStateChangedCallback(
+ any(HandlerExecutor.class), frozenCaptor.capture());
+ mUidFrozenStateCallback = frozenCaptor.getValue();
// Unable to mock mMockContext to return a mock stats manager.
// So just mocking the whole MetricsHelper instance.
@@ -3744,79 +3739,11 @@ public final class AlarmManagerServiceTest {
testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_RARE);
}
- @Test
- public void exactListenerAlarmsRemovedOnCached() {
- mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
-
- setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT,
- TEST_CALLING_UID);
- setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
- setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
- TEST_CALLING_UID, null);
- setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
-
- setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT,
- TEST_CALLING_UID_2);
- setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
- setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
- TEST_CALLING_UID_2, null);
- setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null);
-
- assertEquals(8, mService.mAlarmStore.size());
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(7, mService.mAlarmStore.size());
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(6, mService.mAlarmStore.size());
- }
-
- @Test
- public void alarmCountOnListenerCached() {
- mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
-
- // Set some alarms for TEST_CALLING_UID.
- final int numExactListenerUid1 = 14;
- for (int i = 0; i < numExactListenerUid1; i++) {
- setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
- getNewListener(() -> {}));
- }
- setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
- setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent());
- setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
-
- // Set some alarms for TEST_CALLING_UID_2.
- final int numExactListenerUid2 = 9;
- for (int i = 0; i < numExactListenerUid2; i++) {
- setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
- getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2);
- }
- setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
- setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
- TEST_CALLING_UID_2, null);
-
- assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
- }
-
private void executeUidFrozenStateCallback(int[] uids, int[] frozenStates) {
assertNotNull(mUidFrozenStateCallback);
mUidFrozenStateCallback.onUidFrozenStateChanged(uids, frozenStates);
}
- @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
@DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
@Test
public void exactListenerAlarmsRemovedOnFrozen() {
@@ -3848,7 +3775,6 @@ public final class AlarmManagerServiceTest {
assertEquals(6, mService.mAlarmStore.size());
}
- @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
@DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
@Test
public void alarmCountOnListenerFrozen() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 809e13c65225..6defadf44d05 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -29,6 +29,7 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.content.Intent.FILL_IN_ACTION;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.UserHandle.USER_ALL;
import static android.util.DebugUtils.valueToString;
@@ -79,6 +80,7 @@ import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.ForegroundServiceDelegationOptions;
@@ -86,6 +88,8 @@ import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.SyncNotedAppOp;
+import android.app.backup.BackupAnnotations;
+import android.content.ClipData;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -97,6 +101,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -104,11 +109,14 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IProgressListener;
+import android.os.IpcDataCache;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -131,6 +139,7 @@ import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
import com.android.server.appop.AppOpsService;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerService;
@@ -197,6 +206,16 @@ public class ActivityManagerServiceTest {
private static final String TEST_AUTHORITY = "test_authority";
private static final String TEST_MIME_TYPE = "application/test_type";
+ private static final Uri TEST_URI = Uri.parse("content://com.example/people");
+ private static final int TEST_CREATOR_UID = 12345;
+ private static final String TEST_CREATOR_PACKAGE = "android.content.testCreatorPackage";
+ private static final String TEST_TYPE = "testType";
+ private static final String TEST_IDENTIFIER = "testIdentifier";
+ private static final String TEST_CATEGORY = "testCategory";
+ private static final String TEST_LAUNCH_TOKEN = "testLaunchToken";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ "TestClass");
+ private static final int ALL_SET_FLAG = 0xFFFFFFFF;
private static final int[] UID_RECORD_CHANGES = {
UidRecord.CHANGE_PROCSTATE,
@@ -226,6 +245,7 @@ public class ActivityManagerServiceTest {
@Mock private PackageManagerInternal mPackageManagerInternal;
@Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@Mock private NotificationManagerInternal mNotificationManagerInternal;
+ @Mock private JobSchedulerInternal mJobSchedulerInternal;
@Mock private ContentResolver mContentResolver;
private TestInjector mInjector;
@@ -247,6 +267,7 @@ public class ActivityManagerServiceTest {
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+ LocalServices.addService(JobSchedulerInternal.class, mJobSchedulerInternal);
doReturn(new ComponentName("", "")).when(mPackageManagerInternal)
.getSystemUiServiceComponent();
@@ -277,9 +298,9 @@ public class ActivityManagerServiceTest {
// Required for updating DeviceConfig.
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
- .adoptShellPermissionIdentity(
- Manifest.permission.READ_DEVICE_CONFIG,
- Manifest.permission.WRITE_DEVICE_CONFIG);
+ .adoptShellPermissionIdentity(Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
sProcessListSettingsListener = mAms.mProcessList.getProcessListSettingsListener();
assertThat(sProcessListSettingsListener).isNotNull();
}
@@ -306,6 +327,7 @@ public class ActivityManagerServiceTest {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.removeServiceForTest(JobSchedulerInternal.class);
if (mMockingSession != null) {
mMockingSession.finishMocking();
@@ -952,28 +974,37 @@ public class ActivityManagerServiceTest {
@Test
@SuppressWarnings("GuardedBy")
public void testBroadcastStickyIntent_verifyTypeNotResolved() throws Exception {
- final Intent intent = new Intent(TEST_ACTION1);
- final Uri uri = new Uri.Builder()
- .scheme(SCHEME_CONTENT)
- .authority(TEST_AUTHORITY)
- .path("green")
- .build();
- intent.setData(uri);
- broadcastIntent(intent, null, true, TEST_MIME_TYPE, USER_ALL);
- assertStickyBroadcasts(mAms.getStickyBroadcastsForTest(TEST_ACTION1, USER_ALL),
- StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
- TEST_MIME_TYPE));
- when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
+ MockitoSession mockitoSession =
+ ExtendedMockito.mockitoSession().mockStatic(IpcDataCache.class).startMocking();
- addUidRecord(TEST_UID, TEST_PACKAGE);
- final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
- final IntentFilter intentFilter = new IntentFilter(TEST_ACTION1);
- intentFilter.addDataType(TEST_MIME_TYPE);
- final Intent resultIntent = mAms.registerReceiverWithFeature(procRecord.getThread(),
- TEST_PACKAGE, null, null, null, intentFilter, null, TEST_USER,
- Context.RECEIVER_EXPORTED);
- assertNotNull(resultIntent);
- verify(mContentResolver, never()).getType(any());
+ try {
+ final Intent intent = new Intent(TEST_ACTION1);
+ final Uri uri = new Uri.Builder()
+ .scheme(SCHEME_CONTENT)
+ .authority(TEST_AUTHORITY)
+ .path("green")
+ .build();
+ intent.setData(uri);
+ broadcastIntent(intent, null, true, TEST_MIME_TYPE, USER_ALL);
+ assertStickyBroadcasts(mAms.getStickyBroadcastsForTest(TEST_ACTION1, USER_ALL),
+ StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
+ TEST_MIME_TYPE));
+ when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
+ ExtendedMockito.doNothing().when(
+ () -> IpcDataCache.invalidateCache(anyString(), anyString()));
+
+ addUidRecord(TEST_UID, TEST_PACKAGE);
+ final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+ final IntentFilter intentFilter = new IntentFilter(TEST_ACTION1);
+ intentFilter.addDataType(TEST_MIME_TYPE);
+ final Intent resultIntent = mAms.registerReceiverWithFeature(procRecord.getThread(),
+ TEST_PACKAGE, null, null, null, intentFilter, null, TEST_USER,
+ Context.RECEIVER_EXPORTED);
+ assertNotNull(resultIntent);
+ verify(mContentResolver, never()).getType(any());
+ } finally {
+ mockitoSession.finishMocking();
+ }
}
@SuppressWarnings("GuardedBy")
@@ -1300,6 +1331,129 @@ public class ActivityManagerServiceTest {
.containsExactly(new Pair<>(USER_ID, 42));
}
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testAddCreatorToken() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT0", extraIntent);
+ Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
+ extraIntent.putExtra("NESTED_INTENT", nestedIntent);
+
+ intent.collectExtraIntentKeys();
+ mAms.addCreatorToken(intent, TEST_PACKAGE);
+
+ ActivityManagerService.IntentCreatorToken token =
+ (ActivityManagerService.IntentCreatorToken) extraIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
+
+ token = (ActivityManagerService.IntentCreatorToken) nestedIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testAddCreatorTokenForFillingIntent() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT0", extraIntent);
+ Intent fillinIntent = new Intent();
+ Intent fillinExtraIntent = new Intent("FILLIN_EXTRA_INTENT_ACTION");
+ fillinIntent.putExtra("FILLIN_EXTRA_INTENT0", fillinExtraIntent);
+
+ fillinIntent.collectExtraIntentKeys();
+ intent.fillIn(fillinIntent, FILL_IN_ACTION);
+
+ mAms.addCreatorToken(fillinIntent, TEST_PACKAGE);
+
+ fillinExtraIntent = intent.getParcelableExtra("FILLIN_EXTRA_INTENT0", Intent.class);
+
+ ActivityManagerService.IntentCreatorToken token =
+ (ActivityManagerService.IntentCreatorToken) fillinExtraIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testCheckCreatorToken() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT", extraIntent);
+ Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
+ extraIntent.putExtra("NESTED_INTENT", nestedIntent);
+
+ intent.collectExtraIntentKeys();
+
+ // mimic client hack and sneak in an extra intent without going thru collectExtraIntentKeys.
+ Intent extraIntent2 = new Intent("EXTRA_INTENT_ACTION2");
+ intent.putExtra("EXTRA_INTENT2", extraIntent2);
+
+ // mock parceling on the client side, unparcling on the system server side, then
+ // addCreatorToken on system server side.
+ final Parcel parcel = Parcel.obtain();
+ intent.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Intent newIntent = new Intent();
+ newIntent.readFromParcel(parcel);
+ intent = newIntent;
+ mAms.addCreatorToken(intent, TEST_PACKAGE);
+ // entering the target app's process.
+ intent.checkCreatorToken();
+
+ Intent extraIntent3 = new Intent("EXTRA_INTENT_ACTION3");
+ intent.putExtra("EXTRA_INTENT3", extraIntent3);
+
+ extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class);
+ extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class);
+ extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class);
+ nestedIntent = extraIntent.getParcelableExtra("NESTED_INTENT", Intent.class);
+
+ assertThat(extraIntent.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
+ assertThat(nestedIntent.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
+ // sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
+ assertThat(extraIntent2.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0);
+ // local created intent should not have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
+ assertThat(extraIntent3.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
+ }
+
+ @Test
+ public void testUseCloneForCreatorTokenAndOriginalIntent_createSameIntentCreatorToken() {
+ Intent testIntent = new Intent(TEST_ACTION1)
+ .setComponent(TEST_COMPONENT)
+ .setDataAndType(TEST_URI, TEST_TYPE)
+ .setIdentifier(TEST_IDENTIFIER)
+ .addCategory(TEST_CATEGORY);
+ testIntent.setOriginalIntent(new Intent(TEST_ACTION2));
+ testIntent.setSelector(new Intent(TEST_ACTION3));
+ testIntent.setSourceBounds(new Rect(0, 0, 100, 100));
+ testIntent.setLaunchToken(TEST_LAUNCH_TOKEN);
+ testIntent.addFlags(ALL_SET_FLAG)
+ .addExtendedFlags(ALL_SET_FLAG);
+ ClipData testClipData = ClipData.newHtmlText("label", "text", "<html/>");
+ testClipData.addItem(new ClipData.Item(new Intent(TEST_ACTION1)));
+ testClipData.addItem(new ClipData.Item(TEST_URI));
+ testIntent.putExtra(TEST_EXTRA_KEY1, TEST_EXTRA_VALUE1);
+
+ ActivityManagerService.IntentCreatorToken tokenForFullIntent =
+ new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+ TEST_CREATOR_PACKAGE, testIntent);
+ ActivityManagerService.IntentCreatorToken tokenForCloneIntent =
+ new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+ TEST_CREATOR_PACKAGE, testIntent.cloneForCreatorToken());
+
+ assertThat(tokenForFullIntent.getKeyFields()).isEqualTo(tokenForCloneIntent.getKeyFields());
+ }
+
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {
@@ -1451,6 +1605,50 @@ public class ActivityManagerServiceTest {
eq(notificationId), anyInt());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void bindBackupAgent_fullBackup_shouldUseRestrictedMode_setsInFullBackup()
+ throws Exception {
+ ActivityManagerService spyAms = spy(mAms);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = TEST_PACKAGE;
+ applicationInfo.processName = TEST_PACKAGE;
+ applicationInfo.uid = TEST_UID;
+ doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+ anyLong(), anyInt());
+ ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+ doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+ spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+ UserHandle.USER_SYSTEM,
+ BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+ true);
+
+ assertThat(appRec.isInFullBackup()).isTrue();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void bindBackupAgent_fullBackup_shouldNotUseRestrictedMode_doesNotSetInFullBackup()
+ throws Exception {
+ ActivityManagerService spyAms = spy(mAms);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = TEST_PACKAGE;
+ applicationInfo.processName = TEST_PACKAGE;
+ applicationInfo.uid = TEST_UID;
+ doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+ anyLong(), anyInt());
+ ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+ doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+ spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+ UserHandle.USER_SYSTEM,
+ BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+ false);
+
+ assertThat(appRec.isInFullBackup()).isFalse();
+ }
+
private static class TestHandler extends Handler {
private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 1cad255b85d7..e678acc092e9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -39,8 +39,11 @@ import android.content.pm.PackageManagerInternal;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Parcel;
import android.os.Process;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import com.android.internal.os.Clock;
@@ -87,6 +90,7 @@ public class ApplicationStartInfoTest {
private static final String APP_1_PACKAGE_NAME = "com.android.test.stub1";
@Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private AppOpsService mAppOpsService;
@Mock private PackageManagerInternal mPackageManagerInt;
@@ -144,6 +148,7 @@ public class ApplicationStartInfoTest {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_COMPONENT)
public void testApplicationStartInfo() throws Exception {
// Make sure we can write to the file.
assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
@@ -167,7 +172,7 @@ public class ApplicationStartInfoTest {
ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
// Case 1: Activity start intent failed
- mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT),
appStartTimestampIntentStarted);
mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
verifyInProgressRecordsSize(1);
@@ -185,7 +190,7 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_TYPE_UNSET, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
- mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted);
+ mAppStartInfoTracker.onActivityIntentFailed(appStartTimestampIntentStarted);
list.clear();
mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
verifyInProgressRecordsSize(0);
@@ -194,7 +199,7 @@ public class ApplicationStartInfoTest {
mAppStartInfoTracker.clearProcessStartInfo(true);
// Case 2: Activity start launch cancelled
- mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT),
appStartTimestampIntentStarted);
list.clear();
mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
@@ -236,12 +241,13 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
ApplicationStartInfo.STARTUP_STATE_ERROR, // startup state
ApplicationStartInfo.START_TYPE_COLD, // state type
- ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode
+ ApplicationStartInfo.START_COMPONENT_ACTIVITY); // start component
mAppStartInfoTracker.clearProcessStartInfo(true);
// Case 3: Activity start success
- mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT),
appStartTimestampIntentStarted);
list.clear();
mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
@@ -255,6 +261,7 @@ public class ApplicationStartInfoTest {
verifyInProgressRecordsSize(1);
assertEquals(list.size(), 1);
+ // The records will now be in both backing data structures, so verify in each.
verifyInProgressApplicationStartInfo(
0, // index
APP_1_PID_1, // pid
@@ -277,7 +284,8 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
ApplicationStartInfo.START_TYPE_COLD, // state type
- ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode
+ ApplicationStartInfo.START_COMPONENT_ACTIVITY); // start component
mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
@@ -300,7 +308,7 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
- mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted,
+ mAppStartInfoTracker.onActivityReportFullyDrawn(appStartTimestampIntentStarted,
appStartTimestampReportFullyDrawn);
list.clear();
mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
@@ -317,7 +325,8 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
ApplicationStartInfo.START_TYPE_COLD, // state type
- ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode
+ ApplicationStartInfo.START_COMPONENT_ACTIVITY); // start component
// Don't clear records for use in subsequent cases.
@@ -347,7 +356,8 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_REASON_SERVICE, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
ApplicationStartInfo.START_TYPE_COLD, // state type
- ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode
+ ApplicationStartInfo.START_COMPONENT_SERVICE); // start component
// Case 5: Create an instance of app1 with a different user started for a broadcast
sleep(1);
@@ -376,7 +386,8 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_REASON_BROADCAST, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
ApplicationStartInfo.START_TYPE_COLD, // state type
- ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode
+ ApplicationStartInfo.START_COMPONENT_BROADCAST); // start component
// Case 6: User 2 gets removed
mAppStartInfoTracker.onPackageRemoved(APP_1_PACKAGE_NAME, APP_1_UID_USER_2, false);
@@ -422,7 +433,9 @@ public class ApplicationStartInfoTest {
ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
ApplicationStartInfo.START_TYPE_COLD, // state type
- ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode
+ ApplicationStartInfo.START_COMPONENT_CONTENT_PROVIDER // start component
+ );
// Case 8: Save and load again
ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>();
@@ -453,6 +466,7 @@ public class ApplicationStartInfoTest {
*/
@SuppressWarnings("GuardedBy")
@Test
+ @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_COMPONENT)
public void testInProgressRecordsLimit() throws Exception {
ProcessRecord app = makeProcessRecord(
APP_1_PID_1, // pid
@@ -466,7 +480,7 @@ public class ApplicationStartInfoTest {
// never exceeds the expected size of MAX_IN_PROGRESS_RECORDS.
for (int i = 0; i < AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS * 2; i++) {
Long startTime = Long.valueOf(i);
- mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), startTime);
+ mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT), startTime);
verifyInProgressRecordsSize(
Math.min(i + 1, AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS));
@@ -567,6 +581,50 @@ public class ApplicationStartInfoTest {
assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
}
+ /**
+ * Test to confirm that parcel read and write implementations match, correctly loading records
+ * with the same values and leaving no data unread.
+ */
+ @Test
+ public void testParcelReadWriteMatch() throws Exception {
+ // Create a start info records with all fields set.
+ ApplicationStartInfo startInfo = new ApplicationStartInfo(1234L);
+ startInfo.setPid(123);
+ startInfo.setRealUid(987);
+ startInfo.setPackageUid(654);
+ startInfo.setDefiningUid(321);
+ startInfo.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+ startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+ startInfo.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+ startInfo.setLaunchMode(ApplicationStartInfo.LAUNCH_MODE_SINGLE_TOP);
+ startInfo.setPackageName(APP_1_PACKAGE_NAME);
+ startInfo.setProcessName(APP_1_PROCESS_NAME);
+ startInfo.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, 999L);
+ startInfo.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME, 888L);
+ startInfo.setForceStopped(true);
+ startInfo.setStartComponent(ApplicationStartInfo.START_COMPONENT_OTHER);
+ startInfo.setIntent(buildIntent(COMPONENT));
+
+ // Write the start info to a parcel.
+ Parcel parcel = Parcel.obtain();
+ startInfo.writeToParcel(parcel, 0 /* flags */);
+
+ // Set the data position back to 0 so it's ready to be read.
+ parcel.setDataPosition(0);
+
+ // Now load the record from the parcel.
+ ApplicationStartInfo startInfoFromParcel = new ApplicationStartInfo(parcel);
+
+ // Make sure there is no unread data remaining in the parcel, and confirm that the loaded
+ // start info object is equal to the one it was written from. Check dataAvail first as if
+ // that check fails then the next check will fail too, but knowing the status of this check
+ // will tell us that we're missing a read or write. Check the objects are equals second as
+ // if the avail check passes and equals fails, then we know we're reading all the data just
+ // not to the correct fields.
+ assertEquals(0, parcel.dataAvail());
+ assertTrue(startInfo.equals(startInfoFromParcel));
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
@@ -618,6 +676,10 @@ public class ApplicationStartInfoTest {
}
}
+ /**
+ * Convenience helper to access the record from the in progress data structure. Only applies for
+ * activity starts.
+ */
private void verifyInProgressApplicationStartInfo(int index,
Integer pid, Integer uid, Integer packageUid,
Integer definingUid, String processName,
@@ -625,14 +687,15 @@ public class ApplicationStartInfoTest {
synchronized (mAppStartInfoTracker.mLock) {
verifyApplicationStartInfo(mAppStartInfoTracker.mInProgressRecords.valueAt(index),
pid, uid, packageUid, definingUid, processName, reason, startupState,
- startType, launchMode);
+ startType, launchMode, ApplicationStartInfo.START_COMPONENT_ACTIVITY);
}
}
private void verifyApplicationStartInfo(ApplicationStartInfo info,
Integer pid, Integer uid, Integer packageUid,
Integer definingUid, String processName,
- Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+ Integer reason, Integer startupState, Integer startType, Integer launchMode,
+ Integer startComponent) {
assertNotNull(info);
if (pid != null) {
@@ -662,6 +725,9 @@ public class ApplicationStartInfoTest {
if (launchMode != null) {
assertEquals(launchMode.intValue(), info.getLaunchMode());
}
+ if (startComponent != null) {
+ assertEquals(startComponent.intValue(), info.getStartComponent());
+ }
}
private class TestInjector extends Injector {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 93066d8d113a..67475335fe51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -213,7 +213,7 @@ public class AsyncProcessStartTest {
any(), any(), any(),
any(), any(),
any(), any(),
- any(), any(),
+ any(), any(), any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
@@ -277,7 +277,7 @@ public class AsyncProcessStartTest {
null, null,
null,
null, null, null,
- null, null, null,
+ null, null, null, null,
0, 0);
// Sleep until timeout should have triggered
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index 586191702578..5eb23a24908d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.annotation.NonNull;
@@ -40,6 +41,7 @@ import android.os.TestLooperManager;
import android.os.UserHandle;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -52,11 +54,12 @@ import com.android.server.AlarmManagerInternal;
import com.android.server.DropBoxManagerInternal;
import com.android.server.LocalServices;
import com.android.server.appop.AppOpsService;
+import com.android.server.compat.PlatformCompat;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.Rule;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -96,6 +99,9 @@ public abstract class BaseBroadcastQueueTest {
.spyStatic(ProcessList.class)
.build();
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -111,6 +117,8 @@ public abstract class BaseBroadcastQueueTest {
AlarmManagerInternal mAlarmManagerInt;
@Mock
ProcessList mProcessList;
+ @Mock
+ PlatformCompat mPlatformCompat;
@Mock
AppStartInfoTracker mAppStartInfoTracker;
@@ -164,7 +172,7 @@ public abstract class BaseBroadcastQueueTest {
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
- realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
+ realAms.mOomAdjuster.mCachedAppOptimizer = mock(CachedAppOptimizer.class);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
realAms.mPackageManagerInt = mPackageManagerInt;
@@ -177,6 +185,11 @@ public abstract class BaseBroadcastQueueTest {
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
+
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
}
public void tearDown() throws Exception {
@@ -286,7 +299,8 @@ public abstract class BaseBroadcastQueueTest {
filter.setPriority(priority);
final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
receiverList.app.info.packageName, null, null, null, receiverList.uid,
- receiverList.userId, false, false, true);
+ receiverList.userId, false, false, true, receiverList.app.info,
+ mock(PlatformCompat.class));
receiverList.add(res);
return res;
}
@@ -295,4 +309,8 @@ public abstract class BaseBroadcastQueueTest {
app.mOptRecord.setPendingFreeze(pendingFreeze);
app.mOptRecord.setFrozen(frozen);
}
+
+ ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+ return test -> (test.uid == uid);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
new file mode 100644
index 000000000000..5d106ace71dd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.am;
+
+import static android.content.IntentFilter.SYSTEM_HIGH_PRIORITY;
+import static android.content.IntentFilter.SYSTEM_LOW_PRIORITY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.compat.PlatformCompat;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class BroadcastFilterTest {
+ private static final int TEST_APP_UID = Process.FIRST_APPLICATION_UID + 42;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ PlatformCompat mPlatformCompat;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
+ public void testCalculateAdjustedPriority() {
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForSystemUid(priorityPair.first, priorityPair.second);
+ }
+ }
+
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY - 1),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY + 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY + 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForAppUid(priorityPair.first, priorityPair.second);
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
+ public void testCalculateAdjustedPriority_withChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
+
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForSystemUid(priorityPair.first, priorityPair.second);
+ }
+ }
+
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForAppUid(priorityPair.first, priorityPair.second);
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
+ public void testCalculateAdjustedPriority_withFlagDisabled() {
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForSystemUid(priorityPair.first, priorityPair.second);
+ }
+ }
+
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForAppUid(priorityPair.first, priorityPair.second);
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
+ public void testCalculateAdjustedPriority_withFlagDisabled_withChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
+
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForSystemUid(priorityPair.first, priorityPair.second);
+ }
+ }
+
+ {
+ // Pairs of {initial-priority, expected-adjusted-priority}
+ final Pair<Integer, Integer>[] priorities = new Pair[] {
+ Pair.create(SYSTEM_HIGH_PRIORITY, SYSTEM_HIGH_PRIORITY),
+ Pair.create(SYSTEM_LOW_PRIORITY, SYSTEM_LOW_PRIORITY),
+ Pair.create(SYSTEM_HIGH_PRIORITY + 1, SYSTEM_HIGH_PRIORITY + 1),
+ Pair.create(SYSTEM_LOW_PRIORITY - 1, SYSTEM_LOW_PRIORITY - 1),
+ Pair.create(SYSTEM_HIGH_PRIORITY - 2, SYSTEM_HIGH_PRIORITY - 2),
+ Pair.create(SYSTEM_LOW_PRIORITY + 2, SYSTEM_LOW_PRIORITY + 2)
+ };
+ for (Pair<Integer, Integer> priorityPair : priorities) {
+ assertAdjustedPriorityForAppUid(priorityPair.first, priorityPair.second);
+ }
+ }
+ }
+
+ private void assertAdjustedPriorityForSystemUid(int priority, int expectedAdjustedPriority) {
+ assertAdjustedPriority(Process.SYSTEM_UID, priority, expectedAdjustedPriority);
+ }
+
+ private void assertAdjustedPriorityForAppUid(int priority, int expectedAdjustedPriority) {
+ assertAdjustedPriority(TEST_APP_UID, priority, expectedAdjustedPriority);
+ }
+
+ private void assertAdjustedPriority(int owningUid, int priority, int expectedAdjustedPriority) {
+ final String errorMsg = String.format("owner=%d; actualPriority=%d; expectedPriority=%d",
+ owningUid, priority, expectedAdjustedPriority);
+ assertWithMessage(errorMsg).that(BroadcastFilter.calculateAdjustedPriority(
+ owningUid, priority, mock(ApplicationInfo.class), mPlatformCompat))
+ .isEqualTo(expectedAdjustedPriority);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 100b54897573..82237bca2e34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
@@ -46,10 +47,10 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -65,7 +66,6 @@ import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.BundleMerger;
@@ -73,6 +73,8 @@ import android.os.DropBoxManager;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -182,12 +184,11 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
return mock(Intent.class);
}
- private static ResolveInfo makeMockManifestReceiver() {
- return mock(ResolveInfo.class);
- }
-
private static BroadcastFilter makeMockRegisteredReceiver() {
- return mock(BroadcastFilter.class);
+ final BroadcastFilter filter = mock(BroadcastFilter.class);
+ final ApplicationInfo info = makeApplicationInfo(PACKAGE_ORANGE);
+ doReturn(info).when(filter).getApplicationInfo();
+ return filter;
}
private BroadcastRecord makeBroadcastRecord(Intent intent) {
@@ -214,7 +215,8 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, TEST_UID, false,
null, null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
- BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
+ BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN,
+ mPlatformCompat);
}
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
@@ -646,7 +648,8 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
@Test
public void testRunnableAt_Cached_Manifest() {
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
- List.of(makeMockManifestReceiver()), null, false), REASON_CONTAINS_MANIFEST);
+ List.of(makeManifestReceiver(PACKAGE_RED, CLASS_RED)), null, false),
+ REASON_CONTAINS_MANIFEST);
}
@Test
@@ -679,6 +682,19 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_ALARM);
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testRunnableAt_Cached_Prioritized_NonDeferrable_flagDisabled() {
+ final List receivers = List.of(
+ withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+ receivers, null, false), REASON_CONTAINS_PRIORITIZED);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testRunnableAt_Cached_Prioritized_NonDeferrable() {
final List receivers = List.of(
@@ -687,6 +703,32 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
final BroadcastOptions options = BroadcastOptions.makeBasic()
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+ receivers, null, false), REASON_CONTAINS_MANIFEST);
+ }
+
+ @Test
+ public void testRunnableAt_Cached_Ordered_NonDeferrable() {
+ final List receivers = List.of(
+ withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+ receivers, mock(IIntentReceiver.class), true), REASON_CONTAINS_ORDERED);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testRunnableAt_Cached_Prioritized_NonDeferrable_changeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
+ final List receivers = List.of(
+ withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
receivers, null, false), REASON_CONTAINS_PRIORITIZED);
}
@@ -1136,6 +1178,63 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(blueQueue, List.of(screenOn));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryGroupPolicy_prioritized_diffReceivers_flagDisabled() {
+ final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+ final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+ final BroadcastOptions screenOnOffOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey("screenOnOff", Intent.ACTION_SCREEN_ON);
+
+ final Object greenReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10);
+ final Object redReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_RED, CLASS_RED), 5);
+ final Object blueReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ final BroadcastProcessQueue blueQueue = mImpl.getProcessQueue(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE));
+ verifyPendingRecords(greenQueue, List.of(screenOff));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOff));
+
+ assertTrue(greenQueue.isEmpty());
+ assertTrue(redQueue.isEmpty());
+ assertTrue(blueQueue.isEmpty());
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_prioritized_diffReceivers_flagDisabled");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
@Test
public void testDeliveryGroupPolicy_prioritized_diffReceivers() {
final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
@@ -1173,6 +1272,65 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
List.of(greenReceiver, redReceiver, blueReceiver), false));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_prioritized_diffReceivers");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryGroupPolicy_prioritized_diffReceivers_changeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
+
+ final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+ final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+ final BroadcastOptions screenOnOffOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey("screenOnOff", Intent.ACTION_SCREEN_ON);
+
+ final Object greenReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10);
+ final Object redReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_RED, CLASS_RED), 5);
+ final Object blueReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ final BroadcastProcessQueue blueQueue = mImpl.getProcessQueue(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE));
+ verifyPendingRecords(greenQueue, List.of(screenOff));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOff));
+
+ assertTrue(greenQueue.isEmpty());
+ assertTrue(redQueue.isEmpty());
+ assertTrue(blueQueue.isEmpty());
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
verifyPendingRecords(redQueue, List.of(screenOff));
verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
@@ -1569,8 +1727,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(redQueue, List.of(userPresent, timeTick));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
- public void testDeliveryDeferredForCached() throws Exception {
+ public void testDeliveryDeferredForCached_flagDisabled() throws Exception {
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
@@ -1664,8 +1823,217 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
}, false /* andRemove */);
}
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryDeferredForCached_changeIdDisabled() throws Exception {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
+
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+ List.of(makeRegisteredReceiver(greenProcess, 0)));
+
+ final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ final BroadcastOptions optionsBatteryChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged,
+ optionsBatteryChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 10),
+ makeRegisteredReceiver(redProcess, 0)),
+ false /* ordered */);
+
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+ mImpl.enqueueBroadcastLocked(batteryChangedRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ true /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ // Once the broadcasts to green process are deferred, broadcasts to red process
+ // shouldn't be blocked anymore.
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to green process should be deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 0)));
+ mImpl.enqueueBroadcastLocked(packageChangedRecord);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to the green process, including the newly enqueued one, should be
+ // deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ false /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ }
+
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryDeferredForCached_withInfiniteDeferred_flagDisabled() throws Exception {
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, optionsTimeTick,
+ List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */);
+
+ final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ final BroadcastOptions optionsBatteryChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged,
+ optionsBatteryChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 10),
+ makeRegisteredReceiver(redProcess, 0)),
+ false /* ordered */);
+
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+ mImpl.enqueueBroadcastLocked(batteryChangedRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ true /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ // Once the broadcasts to green process are deferred, broadcasts to red process
+ // shouldn't be blocked anymore.
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to green process should be deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ final BroadcastOptions optionsPackageChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged,
+ optionsPackageChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */);
+ mImpl.enqueueBroadcastLocked(packageChangedRecord);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to the green process, including the newly enqueued one, should be
+ // deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ false /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
- public void testDeliveryDeferredForCached_withInfiniteDeferred() throws Exception {
+ public void testDeliveryDeferredForCached_withInfiniteDeferred_changeIdDisabled()
+ throws Exception {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
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 3aaf2e5c61a6..ea80f283793e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -21,6 +21,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.UserHandle.USER_SYSTEM;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
@@ -45,7 +46,6 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -77,6 +77,8 @@ import android.os.IBinder;
import android.os.PowerExemptionManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArrayMap;
import android.util.Log;
@@ -446,7 +448,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
AppOpsManager.OP_NONE, options, receivers, callerApp, resultTo,
Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId,
- BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
+ BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN,
+ mPlatformCompat);
}
private void assertHealth() {
@@ -1495,7 +1498,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(),
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, null,
Activity.RESULT_OK, null, null, false, false, false, UserHandle.USER_SYSTEM,
- backgroundStartPrivileges, false, null, PROCESS_STATE_UNKNOWN);
+ backgroundStartPrivileges, false, null, PROCESS_STATE_UNKNOWN, mPlatformCompat);
enqueueBroadcast(r);
waitForIdle();
@@ -1550,8 +1553,10 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
/**
* Verify that when dispatching we respect tranches of priority.
*/
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("DistinctVarargsChecker")
@Test
- public void testPriority() throws Exception {
+ public void testPriority_flagDisabled() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -1594,6 +1599,107 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}
/**
+ * Verify that when dispatching we respect tranches of priority.
+ */
+ @SuppressWarnings("DistinctVarargsChecker")
+ @Test
+ public void testOrdered_withPriorities() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ // Enqueue a normal broadcast that will go to several processes, and
+ // then enqueue a foreground broadcast that risks reordering
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+ enqueueBroadcast(makeOrderedBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 10),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeRegisteredReceiver(receiverYellowApp, -10)),
+ orderedResultTo, null));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp))));
+ waitForIdle();
+
+ // Ignore the final foreground broadcast
+ mScheduledBroadcasts.remove(makeScheduledBroadcast(receiverBlueApp, airplane));
+ assertEquals(6, mScheduledBroadcasts.size());
+
+ // We're only concerned about enforcing ordering between tranches;
+ // within a tranche we're okay with reordering
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverGreenApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0)));
+ }
+
+ /**
+ * Verify that when dispatching we respect tranches of priority.
+ */
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("DistinctVarargsChecker")
+ @Test
+ public void testPriority_changeIdDisabled() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(receiverBlueApp.uid)));
+
+ // Enqueue a normal broadcast that will go to several processes, and
+ // then enqueue a foreground broadcast that risks reordering
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 10),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeRegisteredReceiver(receiverYellowApp, -10))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp))));
+ waitForIdle();
+
+ // Ignore the final foreground broadcast
+ mScheduledBroadcasts.remove(makeScheduledBroadcast(receiverBlueApp, airplane));
+ assertEquals(5, mScheduledBroadcasts.size());
+
+ // We're only concerned about enforcing ordering between tranches;
+ // within a tranche we're okay with reordering
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverGreenApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0)));
+ }
+
+ /**
* Verify prioritized receivers work as expected with deferrable broadcast - broadcast to
* app in cached state should be deferred and the rest should be delivered as per the priority
* order.
@@ -2305,8 +2411,35 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
.isLessThan(getReceiverScheduledTime(timeTickRecord, receiverBlue));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testPrioritizedBroadcastDelivery_uidForeground_flagDisabled() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
+ ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
+ final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp,
+ List.of(receiverBlue, receiverGreen));
+
+ enqueueBroadcast(prioritizedRecord);
+
+ waitForIdle();
+ // Verify that uid foreground-ness does not impact that delivery of prioritized broadcast.
+ // That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp.
+ assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen))
+ .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
+ }
+
@Test
- public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
+ public void testOrderedBroadcastDelivery_uidForeground() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2319,6 +2452,38 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
+ final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+ final BroadcastRecord prioritizedRecord = makeOrderedBroadcastRecord(timeTick, callerApp,
+ List.of(receiverBlue, receiverGreen), resultTo, null);
+
+ enqueueBroadcast(prioritizedRecord);
+
+ waitForIdle();
+ // Verify that uid foreground-ness does not impact that delivery of prioritized broadcast.
+ // That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp.
+ assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen))
+ .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testPrioritizedBroadcastDelivery_uidForeground_changeIdDisabled() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(receiverBlueApp.uid)));
+
+ mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
+ ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp,
List.of(receiverBlue, receiverGreen));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 8cd0da721364..8482fd609d05 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -18,11 +18,13 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
@@ -33,6 +35,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
@@ -46,13 +51,20 @@ import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.SubscriptionManager;
import androidx.test.filters.SmallTest;
+import com.android.server.compat.PlatformCompat;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@@ -73,6 +85,9 @@ import java.util.function.BiFunction;
public class BroadcastRecordTest {
private static final String TAG = "BroadcastRecordTest";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int USER0 = UserHandle.USER_SYSTEM;
private static final String PACKAGE1 = "pkg1";
private static final String PACKAGE2 = "pkg2";
@@ -89,10 +104,14 @@ public class BroadcastRecordTest {
@Mock BroadcastQueue mQueue;
@Mock ProcessRecord mProcess;
+ @Mock PlatformCompat mPlatformCompat;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
}
@Test
@@ -108,13 +127,13 @@ public class BroadcastRecordTest {
assertArrayEquals(new int[] {-1},
calculateBlockedUntilBeyondCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+ createResolveInfo(PACKAGE1, getAppId(1), 0)), false, mPlatformCompat));
assertArrayEquals(new int[] {-1},
calculateBlockedUntilBeyondCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+ createResolveInfo(PACKAGE1, getAppId(1), -10)), false, mPlatformCompat));
assertArrayEquals(new int[] {-1},
calculateBlockedUntilBeyondCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
+ createResolveInfo(PACKAGE1, getAppId(1), 10)), false, mPlatformCompat));
}
@Test
@@ -128,18 +147,19 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10))));
- assertArrayEquals(new int[] {-1,-1,-1},
+ assertArrayEquals(new int[] {-1, -1, -1},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
- assertArrayEquals(new int[] {-1,-1,-1},
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
- createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), 10)), false, mPlatformCompat));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_Yes() {
assertTrue(isPrioritized(List.of(
@@ -151,18 +171,203 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 0))));
- assertArrayEquals(new int[] {0,1,2},
+ assertArrayEquals(new int[] {0, 1, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 3, 3},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities() {
+ assertFalse(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertFalse(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {-1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withFirstUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 1, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1, 1, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withLastUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 0, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 3, 3},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 1, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 1, 0},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 2, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(4), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withMultipleUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 1, 2},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
- assertArrayEquals(new int[] {0,0,2,3,3},
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 1, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 2, 2},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 20),
createResolveInfo(PACKAGE2, getAppId(2), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
createResolveInfo(PACKAGE3, getAppId(3), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ createResolveInfo(PACKAGE3, getAppId(4), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1, 1, 3},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0)), false, mPlatformCompat));
}
@Test
@@ -246,6 +451,77 @@ public class BroadcastRecordTest {
assertTerminalDeferredBeyond(r, 3, 0, 3);
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testSetDeliveryState_DeferUntilActive_flagDisabled() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10)));
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+ // Verify deferred counts ratchet up, but we're not "beyond" the first
+ // still-pending receiver
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+ // We're still not "beyond" the first still-pending receiver, even when
+ // we finish a receiver later in the first tranche
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+ // Completing that last item in first tranche means we now unblock the
+ // second tranche, and since it's entirely deferred, the third traunche
+ // is unblocked too
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+ // Moving a deferred item in an earlier tranche back to being pending
+ // doesn't change the fact that we've already moved beyond it
+ r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 5, 7);
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+ // Completing middle pending item is enough to fast-forward to end
+ r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+ // Moving everyone else directly into a finished state updates all the
+ // terminal counters
+ r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 9, 0, 9);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testSetDeliveryState_DeferUntilActive() {
final BroadcastRecord r = createBroadcastRecord(
@@ -259,6 +535,78 @@ public class BroadcastRecordTest {
createResolveInfoWithPriority(-10),
createResolveInfoWithPriority(-10),
createResolveInfoWithPriority(-10)));
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+ // Verify deferred counts ratchet up, but we're not "beyond" the first
+ // still-pending receiver
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+ // We're still not "beyond" the first still-pending receiver, even when
+ // we finish a receiver later in the first tranche
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+ // Completing that last item in first tranche means we now unblock the
+ // second tranche, and since it's entirely deferred, the third traunche
+ // is unblocked too
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+ // Moving a deferred item in an earlier tranche back to being pending
+ // doesn't change the fact that we've already moved beyond it
+ r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 5, 7);
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+ // Completing middle pending item is enough to fast-forward to end
+ r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+ // Moving everyone else directly into a finished state updates all the
+ // terminal counters
+ r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 9, 0, 9);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testSetDeliveryState_DeferUntilActive_changeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE1, getAppId(1), -10)));
assertBlocked(r, false, false, false, true, true, true, true, true, true);
assertTerminalDeferredBeyond(r, 0, 0, 0);
@@ -602,6 +950,66 @@ public class BroadcastRecordTest {
assertTrue(record3.matchesDeliveryGroup(record1));
}
+ @Test
+ public void testCalculateChangeStateForReceivers() {
+ assertArrayEquals(new boolean[] {true, true, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {true, true, true, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+ assertArrayEquals(new boolean[] {false, true, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {false, true, false, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
+ assertArrayEquals(new boolean[] {false, false, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {false, true, false, false, false, true},
+ calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE3, getAppId(3)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
+ assertArrayEquals(new boolean[] {false, false, false}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {false, false, false, false, false, false},
+ calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE3, getAppId(3)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ }
+
+ private boolean[] calculateChangeState(List<Object> receivers) {
+ return BroadcastRecord.calculateChangeStateForReceivers(receivers,
+ LIMIT_PRIORITY_SCOPE, mPlatformCompat);
+ }
+
private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
String packageName, int userId) {
record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
@@ -753,16 +1161,17 @@ public class BroadcastRecordTest {
BackgroundStartPrivileges.NONE,
false /* timeoutExempt */,
filterExtrasForReceiver,
- PROCESS_STATE_UNKNOWN);
+ PROCESS_STATE_UNKNOWN,
+ mPlatformCompat);
}
private static int getAppId(int i) {
return Process.FIRST_APPLICATION_UID + i;
}
- private static boolean isPrioritized(List<Object> receivers) {
+ private boolean isPrioritized(List<Object> receivers) {
return BroadcastRecord.isPrioritized(
- calculateBlockedUntilBeyondCount(receivers, false), false);
+ calculateBlockedUntilBeyondCount(receivers, false, mPlatformCompat), false);
}
private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
@@ -778,4 +1187,8 @@ public class BroadcastRecordTest {
assertEquals("deferred", expectedDeferredCount, r.deferredCount);
assertEquals("beyond", expectedBeyondCount, r.beyondCount);
}
+
+ private ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+ return test -> (test.uid == uid);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2107406f9b13..1efe4707fc11 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_AP
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -104,10 +105,12 @@ import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.server.LocalServices;
@@ -129,6 +132,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* Test class for {@link OomAdjuster}.
@@ -167,14 +171,18 @@ public class MockingOomAdjusterTests {
private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ
+ ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
private static int sFirstUiCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 10;
- private static int sFirstNonUiCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 20;
- private static int sUiTierSize = 5;
private Context mContext;
+ private ProcessStateController mProcessStateController;
+ private ActiveUids mActiveUids;
private PackageManagerInternal mPackageManagerInternal;
private ActivityManagerService mService;
+ private TestCachedAppOptimizer mTestCachedAppOptimizer;
private OomAdjusterInjector mInjector = new OomAdjusterInjector();
+ private int mUiTierSize;
+ private int mFirstNonUiCachedAdj;
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -225,29 +233,40 @@ public class MockingOomAdjusterTests {
mock(BatteryStatsService.class));
setFieldValue(ActivityManagerService.class, mService, "mInjector",
new ActivityManagerService.Injector(mContext));
+ setFieldValue(ActivityManagerService.class, mService, "mPhantomProcessList",
+ new PhantomProcessList(mService));
doReturn(mock(AppOpsManager.class)).when(mService).getAppOpsManager();
doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class));
doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY);
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
+
doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
anyInt());
doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
anyBoolean());
- mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ
- ? new OomAdjusterModernImpl(mService, mService.mProcessList,
- new ActiveUids(mService, false), mInjector)
- : new OomAdjuster(mService, mService.mProcessList, new ActiveUids(mService, false),
- mInjector);
+ mActiveUids = new ActiveUids(mService, false);
+ mTestCachedAppOptimizer = new TestCachedAppOptimizer(mService);
+ mProcessStateController = new ProcessStateController.Builder(mService,
+ mService.mProcessList, mActiveUids)
+ .useModernOomAdjuster(mService.mConstants.ENABLE_NEW_OOMADJ)
+ .setCachedAppOptimizer(mTestCachedAppOptimizer)
+ .setOomAdjusterInjector(mInjector)
+ .build();
+ mService.mProcessStateController = mProcessStateController;
+ mService.mOomAdjuster = mService.mProcessStateController.getOomAdjuster();
mService.mOomAdjuster.mAdjSeq = 10000;
mService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC);
+
+ mUiTierSize = mService.mConstants.TIERED_CACHED_ADJ_UI_TIER_SIZE;
+ mFirstNonUiCachedAdj = sFirstUiCachedAdj + mUiTierSize;
}
@SuppressWarnings("GuardedBy")
@After
public void tearDown() {
- mService.mOomAdjuster.resetInternal();
- mService.mOomAdjuster.mActiveUids.clear();
+ mProcessStateController.getOomAdjuster().resetInternal();
+ mActiveUids.clear();
LocalServices.removeServiceForTest(PackageManagerInternal.class);
mInjector.reset();
}
@@ -293,7 +312,7 @@ public class MockingOomAdjusterTests {
private void updateOomAdj(ProcessRecord... apps) {
if (apps.length == 0) {
updateProcessRecordNodes(mService.mProcessList.getLruProcessesLOSP());
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
} else {
updateProcessRecordNodes(Arrays.asList(apps));
if (apps.length == 1) {
@@ -301,10 +320,10 @@ public class MockingOomAdjusterTests {
if (!mService.mProcessList.getLruProcessesLOSP().contains(app)) {
mService.mProcessList.getLruProcessesLOSP().add(app);
}
- mService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
+ mProcessStateController.runUpdate(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
mService.mProcessList.getLruProcessesLOSP().clear();
}
}
@@ -318,9 +337,9 @@ public class MockingOomAdjusterTests {
private void updateOomAdjPending(ProcessRecord... apps) {
setProcessesToLru(apps);
for (ProcessRecord app : apps) {
- mService.mOomAdjuster.enqueueOomAdjTargetLocked(app);
+ mProcessStateController.enqueueUpdateTarget(app);
}
- mService.mOomAdjuster.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
mService.mProcessList.getLruProcessesLOSP().clear();
}
@@ -341,11 +360,10 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- app.mState.setHasTopUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ mProcessStateController.setMaxAdj(app, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(app);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
SCHED_GROUP_RESTRICTED);
@@ -357,9 +375,9 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Awake() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- app.mState.setHasTopUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(app, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
@@ -371,9 +389,9 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Persistent_TopApp() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(app, PERSISTENT_PROC_ADJ);
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(null).when(mService).getTopApp();
@@ -388,7 +406,7 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(null).when(mService).getTopApp();
@@ -401,8 +419,8 @@ public class MockingOomAdjusterTests {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP_SLEEPING).when(mService.mAtmInternal).getTopProcessState();
- app.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
@@ -415,7 +433,7 @@ public class MockingOomAdjusterTests {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(mock(ActiveInstrumentation.class)).when(app).getActiveInstrumentation();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doCallRealMethod().when(app).getActiveInstrumentation();
@@ -431,7 +449,7 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(true).when(mService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(false).when(mService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
@@ -466,8 +484,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_ExecutingService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.startExecutingService(mock(ServiceRecord.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.startExecutingService(app.mServices, mock(ServiceRecord.class));
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
@@ -480,11 +498,11 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP_SLEEPING).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(app);
doReturn(null).when(mService).getTopApp();
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, FOREGROUND_APP_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -498,7 +516,7 @@ public class MockingOomAdjusterTests {
app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
app.mState.setCurAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
@@ -516,7 +534,7 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).hasActivities();
doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE)
.when(wpc).getActivityStateFlags();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -555,7 +573,7 @@ public class MockingOomAdjusterTests {
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).hasRecentTasks();
app.mState.setLastTopTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doCallRealMethod().when(wpc).hasRecentTasks();
@@ -567,9 +585,9 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_FgServiceLocation() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
- /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION, /* hasNoneType=*/false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -582,8 +600,9 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_FgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app.mServices, true, 0, /* hasNoneType=*/
+ true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -599,20 +618,20 @@ public class MockingOomAdjusterTests {
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
+ mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
// SHORT_SERVICE FGS will get IMP_FG and a slightly different recent-adjustment.
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.startService(s);
- app.mServices.setHasForegroundServices(true,
+ mProcessStateController.startService(app.mServices, s);
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -625,9 +644,9 @@ public class MockingOomAdjusterTests {
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true,
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
- app.mServices.startService(s);
+ mProcessStateController.startService(app.mServices, s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- mService.mConstants.TOP_TO_FGS_GRACE_DURATION);
mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -642,21 +661,21 @@ public class MockingOomAdjusterTests {
// SHORT_SERVICE, timed out already.
s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
+ mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis()
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis()
- mService.mConstants.mShortFgsTimeoutDuration
- mService.mConstants.mShortFgsProcStateExtraWaitDuration);
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true,
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
- app.mServices.startService(s);
+ mProcessStateController.startService(app.mServices, s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- mService.mConstants.TOP_TO_FGS_GRACE_DURATION);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -671,8 +690,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_OverlayUi() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setHasOverlayUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasOverlayUi(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
@@ -684,9 +703,10 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_PerceptibleRecent_FgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app.mServices, true, 0, /* hasNoneType=*/
+ true);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -699,7 +719,7 @@ public class MockingOomAdjusterTests {
verify(mService.mHandler).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT, "fg-service");
@@ -722,9 +742,9 @@ public class MockingOomAdjusterTests {
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime;
+ mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s, nowUptime);
s.getConnections().clear();
- app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+ mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(app.mServices);
mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -736,7 +756,7 @@ public class MockingOomAdjusterTests {
verify(mService.mHandler).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstUiCachedAdj : sFirstCachedAdj;
@@ -758,15 +778,15 @@ public class MockingOomAdjusterTests {
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE + 2, mock(IBinder.class));
- s.lastTopAlmostPerceptibleBindRequestUptimeMs =
- nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
- app.mServices.updateHasTopStartedAlmostPerceptibleServices();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s,
+ nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs);
+ mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
// Out of grace period and no valid binding so no adjustment.
@@ -780,16 +800,16 @@ public class MockingOomAdjusterTests {
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- s.lastTopAlmostPerceptibleBindRequestUptimeMs =
- nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
+ mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s,
+ nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs);
s.getConnections().clear();
- app.mServices.updateHasTopStartedAlmostPerceptibleServices();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
}
@@ -800,12 +820,12 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
- system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- system.mState.setHasTopUi(true);
+ mProcessStateController.setMaxAdj(system, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(system, true);
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(system, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
@@ -817,8 +837,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Toast() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setForcingToImportant(new Object());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setForcingToImportant(app, new Object());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -832,7 +852,7 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(false).when(wpc).isHeavyWeightProcess();
@@ -847,7 +867,7 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_HOME, HOME_APP_ADJ, SCHED_GROUP_BACKGROUND);
@@ -861,7 +881,7 @@ public class MockingOomAdjusterTests {
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
@@ -873,7 +893,7 @@ public class MockingOomAdjusterTests {
verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstUiCachedAdj : CACHED_APP_MIN_ADJ;
@@ -886,8 +906,25 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoPending_PreviousApp() {
+ testUpdateOomAdj_PreviousApp(apps -> {
+ for (ProcessRecord app : apps) {
+ mProcessStateController.enqueueUpdateTarget(app);
+ }
+ mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoAll_PreviousApp() {
- final int numberOfApps = 15;
+ testUpdateOomAdj_PreviousApp(apps -> {
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+ final int numberOfApps = 105;
final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
for (int i = 0; i < numberOfApps; i++) {
apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -896,12 +933,13 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
}
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(apps);
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
-
+ updater.accept(apps);
for (int i = 0; i < numberOfApps; i++) {
- assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ final int mruIndex = numberOfApps - i - 1;
+ final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+ assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
SCHED_GROUP_BACKGROUND, "previous");
}
@@ -914,14 +952,14 @@ public class MockingOomAdjusterTests {
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
}
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
for (int i = 0; i < numberOfApps; i++) {
final int mruIndex = numberOfApps - i - 1;
int expectedAdj;
if (mService.mConstants.USE_TIERED_CACHED_ADJ) {
- expectedAdj = (i < numberOfApps - sUiTierSize)
- ? sFirstNonUiCachedAdj : sFirstUiCachedAdj + mruIndex;
+ expectedAdj = (i < numberOfApps - mUiTierSize)
+ ? mFirstNonUiCachedAdj : sFirstUiCachedAdj + mruIndex;
} else {
expectedAdj = CACHED_APP_MIN_ADJ + (mruIndex * 2 * CACHED_APP_IMPORTANCE_LEVELS);
if (expectedAdj > CACHED_APP_MAX_ADJ) {
@@ -938,10 +976,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Backup() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
- backupTarget.app = app;
- doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setBackupTarget(app);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(null).when(mService.mBackupTargets).get(anyInt());
@@ -954,8 +990,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_ClientActivities() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasClientActivities(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasClientActivities(app.mServices, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
@@ -966,8 +1002,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_TreatLikeActivity() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setTreatLikeActivity(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setTreatLikeActivity(app.mServices, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
@@ -981,10 +1017,10 @@ public class MockingOomAdjusterTests {
app.mState.setServiceB(true);
ServiceRecord s = mock(ServiceRecord.class);
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- app.mServices.startService(s);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(app.mServices, s);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_B_ADJ, SCHED_GROUP_BACKGROUND);
@@ -995,8 +1031,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_MaxAdj() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(app, PERCEPTIBLE_LOW_APP_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, PERCEPTIBLE_LOW_APP_ADJ,
@@ -1011,7 +1047,7 @@ public class MockingOomAdjusterTests {
app.mState.setCurRawAdj(SERVICE_ADJ);
app.mState.setCurAdj(SERVICE_ADJ);
doReturn(null).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
@@ -1025,10 +1061,10 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ServiceRecord s = mock(ServiceRecord.class);
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- app.mServices.startService(s);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(app.mServices, s);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
@@ -1043,8 +1079,8 @@ public class MockingOomAdjusterTests {
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
ServiceRecord s = bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY,
mock(IBinder.class));
- s.startRequested = true;
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
updateOomAdj(client, app);
@@ -1062,10 +1098,10 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.mServices.setTreatLikeActivity(true);
+ mProcessStateController.setTreatLikeActivity(client.mServices, true);
bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY
| Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
@@ -1086,7 +1122,7 @@ public class MockingOomAdjusterTests {
mock(ActivityServiceConnectionsHolder.class));
doReturn(client).when(mService).getTopApp();
doReturn(true).when(cr.activity).isActivityVisible();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -1099,11 +1135,11 @@ public class MockingOomAdjusterTests {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
bindService(app, app, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
- ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+ ? mFirstNonUiCachedAdj : sFirstCachedAdj;
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND);
}
@@ -1114,9 +1150,9 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.mServices.setTreatLikeActivity(true);
+ mProcessStateController.setTreatLikeActivity(client.mServices, true);
bindService(app, client, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
@@ -1137,7 +1173,7 @@ public class MockingOomAdjusterTests {
mock(IBinder.class));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(null).when(mService).getTopApp();
@@ -1152,9 +1188,9 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- client.mState.setHasTopUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -1170,8 +1206,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_IMPORTANT, mock(IBinder.class));
- client.mServices.startExecutingService(mock(ServiceRecord.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.startExecutingService(client.mServices, mock(ServiceRecord.class));
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -1188,7 +1224,7 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null, 0, mock(IBinder.class));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(null).when(mService).getTopApp();
@@ -1203,8 +1239,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
@@ -1222,9 +1258,9 @@ public class MockingOomAdjusterTests {
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(client, app);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_RESTRICTED);
@@ -1240,8 +1276,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
@@ -1256,8 +1292,9 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true,
+ 0, /* hasNoneType=*/true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, client.mState.getSetProcState());
@@ -1279,16 +1316,16 @@ public class MockingOomAdjusterTests {
// In order to trick OomAdjuster to think it has a short-service, we need this logic.
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
- s.isForeground = true;
- s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
- client.mServices.startService(s);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setIsForegroundService(s, true);
+ mProcessStateController.setForegroundServiceType(s, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(client.mServices, s);
client.mState.setLastTopTime(SystemClock.uptimeMillis());
- client.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
// Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
@@ -1310,16 +1347,16 @@ public class MockingOomAdjusterTests {
// In order to trick OomAdjuster to think it has a short-service, we need this logic.
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
- s.isForeground = true;
- s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
- app2.mServices.startService(s);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setIsForegroundService(s, true);
+ mProcessStateController.setForegroundServiceType(s, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(app2.mServices, s);
app2.mState.setLastTopTime(SystemClock.uptimeMillis());
- app2.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app2.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app2);
// Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
@@ -1331,7 +1368,7 @@ public class MockingOomAdjusterTests {
// Persistent process
ProcessRecord pers = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- pers.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(pers, PERSISTENT_PROC_ADJ);
// app1, which is bound by pers (which makes it BFGS)
ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
@@ -1358,18 +1395,16 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
- backupTarget.app = client;
- doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setBackupTarget(client);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
- doReturn(null).when(mService.mBackupTargets).get(anyInt());
+ mProcessStateController.stopBackupTarget(UserHandle.getUserId(MOCKAPP2_UID));
assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
assertNoBfsl(app);
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
updateOomAdj(client, app);
assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
@@ -1384,8 +1419,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
- client.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
@@ -1393,14 +1428,39 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoOne_Service_NotPerceptible_AboveClient() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ ProcessRecord service = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
+ bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app, service);
+
+ final int expectedAdj;
+ if (Flags.addModifyRawOomAdjServiceLevel()) {
+ expectedAdj = SERVICE_ADJ;
+ } else {
+ expectedAdj = CACHED_APP_MIN_ADJ;
+ }
+ assertEquals(expectedAdj, app.mState.getSetAdj());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoOne_Service_NotVisible() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
- client.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
@@ -1414,8 +1474,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, 0, mock(IBinder.class));
- client.mState.setHasOverlayUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasOverlayUi(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
@@ -1432,13 +1492,13 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
{
@@ -1451,14 +1511,14 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
{
@@ -1469,13 +1529,13 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
{
@@ -1488,14 +1548,14 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
}
@@ -1507,8 +1567,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, 0, mock(IBinder.class));
- client.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
@@ -1523,8 +1583,8 @@ public class MockingOomAdjusterTests {
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_IMPORTANT_BACKGROUND,
mock(IBinder.class));
- client.mState.setHasOverlayUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasOverlayUi(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
@@ -1536,11 +1596,12 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Provider_Self() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- bindProvider(app, app, null, null, false);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(app, cpr);
updateOomAdj(app);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
- ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+ ? mFirstNonUiCachedAdj : sFirstCachedAdj;
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND);
}
@@ -1551,13 +1612,14 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindProvider(app, client, null, null, false);
- client.mServices.setTreatLikeActivity(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(client, cpr);
+ mProcessStateController.setTreatLikeActivity(client.mServices, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
- ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+ ? mFirstNonUiCachedAdj : sFirstCachedAdj;
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND);
}
@@ -1568,10 +1630,11 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindProvider(app, client, null, null, false);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(client, cpr);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(null).when(mService).getTopApp();
@@ -1585,9 +1648,10 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- bindProvider(app, client, null, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(client, cpr);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1607,17 +1671,18 @@ public class MockingOomAdjusterTests {
// In order to trick OomAdjuster to think it has a short-service, we need this logic.
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
+ mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
- client.mServices.startService(s);
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(client.mServices, s);
client.mState.setLastTopTime(SystemClock.uptimeMillis());
- client.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- /* hasNoneType=*/false);
- bindProvider(app, client, null, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, false);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(client, cpr);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
// Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
@@ -1638,8 +1703,9 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindProvider(app, client, null, null, true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, true);
+ bindProvider(client, cpr);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, FOREGROUND_APP_ADJ,
@@ -1651,9 +1717,24 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_Provider_Retention() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- updateOomAdj(app);
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ final String providerName = "aProvider";
+ // Go through the motions of binding a provider
+ final ContentProviderRecord cpr = createContentProviderRecord(app, providerName, false);
+ final ContentProviderConnection conn = bindProvider(client, cpr);
+ doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
+ doReturn(client).when(mService).getTopApp();
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app);
+
+ assertProcStates(app, PROCESS_STATE_BOUND_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
+
+ unbindProvider(client, cpr, conn);
+ mProcessStateController.removePublishedProvider(app, providerName);
+ final long lastProviderTime = SystemClock.uptimeMillis();
+ mProcessStateController.setLastProviderTime(app, SystemClock.uptimeMillis());
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND, "recent-provider");
@@ -1663,11 +1744,13 @@ public class MockingOomAdjusterTests {
final ArgumentCaptor<Long> followUpTimeCaptor = ArgumentCaptor.forClass(Long.class);
verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
followUpTimeCaptor.capture());
+
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ setProcessesToLru(client, app);
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
- ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+ ? mFirstNonUiCachedAdj : sFirstCachedAdj;
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
"cch-empty");
// Follow up should not have been called again.
@@ -1688,7 +1771,7 @@ public class MockingOomAdjusterTests {
bindService(client, client2, null, null, 0, mock(IBinder.class));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client2).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
doReturn(null).when(mService).getTopApp();
@@ -1707,8 +1790,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1727,8 +1810,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1747,13 +1830,13 @@ public class MockingOomAdjusterTests {
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindService(client2, app, null, null, 0, mock(IBinder.class));
// Note: We add processes to LRU but still call updateOomAdjLocked() with a specific
// processes.
setProcessesToLru(app, client, client2);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1766,8 +1849,8 @@ public class MockingOomAdjusterTests {
assertBfsl(client);
assertBfsl(client2);
- client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, false, 0, false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client2);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
@@ -1790,8 +1873,8 @@ public class MockingOomAdjusterTests {
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client2, client, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1817,8 +1900,8 @@ public class MockingOomAdjusterTests {
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
bindService(client2, client, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1851,8 +1934,8 @@ public class MockingOomAdjusterTests {
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
bindService(client3, client4, null, null, 0, mock(IBinder.class));
bindService(client4, client3, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1883,13 +1966,13 @@ public class MockingOomAdjusterTests {
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindService(client2, app, null, null, 0, mock(IBinder.class));
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1913,9 +1996,9 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -1940,9 +2023,9 @@ public class MockingOomAdjusterTests {
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- client4.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client4, new Object());
bindService(app, client4, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -1965,13 +2048,13 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client4.mServices, true, 0, true);
bindService(app, client4, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1992,12 +2075,12 @@ public class MockingOomAdjusterTests {
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, client3, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2015,9 +2098,10 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null, 0, mock(IBinder.class));
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ final ContentProviderRecord cpr = createContentProviderRecord(client, null, false);
+ bindProvider(client2, cpr);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2035,10 +2119,11 @@ public class MockingOomAdjusterTests {
bindService(app, client, null, null, 0, mock(IBinder.class));
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ final ContentProviderRecord cpr = createContentProviderRecord(client, null, false);
+ bindProvider(client2, cpr);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindService(client2, app, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2053,12 +2138,14 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindProvider(app, client, null, null, false);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(client, cpr);
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ final ContentProviderRecord cpr2 = createContentProviderRecord(client, null, false);
+ bindProvider(client2, cpr2);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2073,13 +2160,16 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindProvider(app, client, null, null, false);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(client, cpr);
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- bindProvider(client2, app, null, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ final ContentProviderRecord cpr2 = createContentProviderRecord(client, null, false);
+ bindProvider(client2, cpr2);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ final ContentProviderRecord cpr3 = createContentProviderRecord(client2, null, false);
+ bindProvider(app, cpr3);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2102,10 +2192,10 @@ public class MockingOomAdjusterTests {
mock(IBinder.class));
bindService(app2, client2, null, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
mock(IBinder.class));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -2126,7 +2216,7 @@ public class MockingOomAdjusterTests {
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -2136,7 +2226,7 @@ public class MockingOomAdjusterTests {
bindService(client2, app1, null, null, 0, mock(IBinder.class));
bindService(app1, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
+ mProcessStateController.setHasForegroundServices(client2.mServices, false, 0, false);
updateOomAdj(app1, client1, client2);
assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -2153,8 +2243,8 @@ public class MockingOomAdjusterTests {
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client2, PERSISTENT_PROC_ADJ);
final ServiceRecord s1 = bindService(app1, client1, null, null,
Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class));
@@ -2178,10 +2268,10 @@ public class MockingOomAdjusterTests {
s2.getConnections().clear();
client1.mServices.removeAllConnections();
client2.mServices.removeAllConnections();
- client1.mState.setMaxAdj(UNKNOWN_ADJ);
- client2.mState.setMaxAdj(UNKNOWN_ADJ);
- client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- client2.mState.setHasOverlayUi(true);
+ mProcessStateController.setMaxAdj(client1, UNKNOWN_ADJ);
+ mProcessStateController.setMaxAdj(client2, UNKNOWN_ADJ);
+ mProcessStateController.setHasForegroundServices(client1.mServices, true, 0, true);
+ mProcessStateController.setHasOverlayUi(client2, true);
bindService(app1, client1, null, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
@@ -2196,10 +2286,10 @@ public class MockingOomAdjusterTests {
assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
- client2.mState.setHasOverlayUi(false);
+ mProcessStateController.setHasOverlayUi(client2, false);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client2).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client2, app2);
assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
@@ -2213,10 +2303,10 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
- app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app1.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
bindService(app1, client1, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
@@ -2234,10 +2324,10 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
- app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app1.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
bindService(app1, client1, null, null, Context.BIND_ALMOST_PERCEPTIBLE,
mock(IBinder.class));
@@ -2254,10 +2344,10 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_PendingFinishAttach() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.setPendingFinishAttach(true);
+ mProcessStateController.setPendingFinishAttach(app, true);
app.mState.setHasForegroundActivities(false);
- mService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+ mProcessStateController.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FOREGROUND_APP_ADJ,
@@ -2269,11 +2359,11 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_TopApp_PendingFinishAttach() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.setPendingFinishAttach(true);
+ mProcessStateController.setPendingFinishAttach(app, true);
app.mState.setHasForegroundActivities(true);
doReturn(app).when(mService).getTopApp();
- mService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+ mProcessStateController.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ,
@@ -2303,40 +2393,40 @@ public class MockingOomAdjusterTests {
client1.setUidRecord(clientUidRecord);
client2.setUidRecord(clientUidRecord);
- client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- client2.mState.setForcingToImportant(new Object());
+ mProcessStateController.setHasForegroundServices(client1.mServices, true, 0, true);
+ mProcessStateController.setForcingToImportant(client2, new Object());
setProcessesToLru(app1, app2, app3, client1, client2);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
final ComponentName cn1 = ComponentName.unflattenFromString(
MOCKAPP_PACKAGENAME + "/.TestService");
final ServiceRecord s1 = bindService(app1, client1, null, null, 0, mock(IBinder.class));
setFieldValue(ServiceRecord.class, s1, "name", cn1);
- s1.startRequested = true;
+ mProcessStateController.setStartRequested(s1, true);
final ComponentName cn2 = ComponentName.unflattenFromString(
MOCKAPP2_PACKAGENAME + "/.TestService");
final ServiceRecord s2 = bindService(app2, client2, null, null, 0, mock(IBinder.class));
setFieldValue(ServiceRecord.class, s2, "name", cn2);
- s2.startRequested = true;
+ mProcessStateController.setStartRequested(s2, true);
final ComponentName cn3 = ComponentName.unflattenFromString(
MOCKAPP5_PACKAGENAME + "/.TestService");
final ServiceRecord s3 = bindService(app3, client1, null, null, 0, mock(IBinder.class));
setFieldValue(ServiceRecord.class, s3, "name", cn3);
- s3.startRequested = true;
+ mProcessStateController.setStartRequested(s3, true);
final ComponentName cn4 = ComponentName.unflattenFromString(
MOCKAPP3_PACKAGENAME + "/.TestService");
final ServiceRecord c2s = makeServiceRecord(client2);
setFieldValue(ServiceRecord.class, c2s, "name", cn4);
- c2s.startRequested = true;
+ mProcessStateController.setStartRequested(c2s, true);
try {
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP_UID, app1UidRecord);
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP2_UID, app2UidRecord);
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP5_UID, app3UidRecord);
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP3_UID, clientUidRecord);
+ mActiveUids.put(MOCKAPP_UID, app1UidRecord);
+ mActiveUids.put(MOCKAPP2_UID, app2UidRecord);
+ mActiveUids.put(MOCKAPP5_UID, app3UidRecord);
+ mActiveUids.put(MOCKAPP3_UID, clientUidRecord);
setServiceMap(s1, MOCKAPP_UID, cn1);
setServiceMap(s2, MOCKAPP2_UID, cn2);
@@ -2354,8 +2444,8 @@ public class MockingOomAdjusterTests {
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app2.mState.getSetProcState());
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, client2.mState.getSetProcState());
- client1.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
- client2.mState.setForcingToImportant(null);
+ mProcessStateController.setHasForegroundServices(client1.mServices, false, 0, false);
+ mProcessStateController.setForcingToImportant(client2, null);
app1UidRecord.reset();
app2UidRecord.reset();
app3UidRecord.reset();
@@ -2379,7 +2469,7 @@ public class MockingOomAdjusterTests {
.getAppStartModeLOSP(anyInt(), any(String.class), anyInt(),
anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
mService.mServices.mServiceMap.clear();
- mService.mOomAdjuster.mActiveUids.clear();
+ mActiveUids.clear();
}
}
@@ -2388,12 +2478,13 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoAll_Unbound() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setForcingToImportant(new Object());
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setForcingToImportant(app, new Object());
+ mProcessStateController.setHasForegroundServices(app2.mServices, true, 0, /* hasNoneType=*/
+ true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -2408,12 +2499,14 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoAll_BoundFgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setForcingToImportant(new Object());
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+
+ mProcessStateController.setForcingToImportant(app, new Object());
+ mProcessStateController.setHasForegroundServices(app2.mServices, true, 0, /* hasNoneType=*/
+ true);
bindService(app, app2, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2435,9 +2528,9 @@ public class MockingOomAdjusterTests {
ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app2, app3, null, null, 0, mock(IBinder.class));
- app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app3.mServices, true, 0, true);
bindService(app3, app, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2, app3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2476,13 +2569,13 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindService(app, app4, null, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindService(app, app5, null, s, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2518,13 +2611,13 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindService(app, app4, null, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindService(app, app5, null, s, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app5, app4, app3, app2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2560,13 +2653,13 @@ public class MockingOomAdjusterTests {
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindService(app, app4, null, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindService(app, app5, null, s, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app3, app4, app2, app, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2603,10 +2696,10 @@ public class MockingOomAdjusterTests {
mock(IBinder.class));
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client3, PERSISTENT_PROC_ADJ);
bindService(app, client3, null, null, Context.BIND_INCLUDE_CAPABILITIES,
mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
final int expected = PROCESS_CAPABILITY_ALL & ~PROCESS_CAPABILITY_BFSL;
@@ -2622,22 +2715,25 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- ContentProviderRecord cr = bindProvider(app, app2, null, null, false);
+ final ContentProviderRecord cpr = createContentProviderRecord(app, null, false);
+ bindProvider(app2, cpr);
ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- bindProvider(app2, app3, null, null, false);
- bindProvider(app3, app, null, null, false);
+ final ContentProviderRecord cpr2 = createContentProviderRecord(app2, null, false);
+ bindProvider(app3, cpr2);
+ final ContentProviderRecord cpr3 = createContentProviderRecord(app3, null, false);
+ bindProvider(app, cpr3);
WindowProcessController wpc = app3.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
- bindProvider(app, app4, cr, null, false);
+ mProcessStateController.setHasOverlayUi(app4, true);
+ bindProvider(app4, cpr);
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- bindProvider(app, app5, cr, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
+ bindProvider(app5, cpr);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2666,22 +2762,22 @@ public class MockingOomAdjusterTests {
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
long now = SystemClock.uptimeMillis();
ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class));
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
s = bindService(app2, app, null, null, 0, mock(IBinder.class));
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
s = mock(ServiceRecord.class);
- s.app = app3;
+ mProcessStateController.setHostProcess(s, app3);
setFieldValue(ServiceRecord.class, s, "connections",
new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
- app3.mServices.startService(s);
+ mProcessStateController.startService(app3.mServices, s);
doCallRealMethod().when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = now;
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
mService.mOomAdjuster.mNumServiceProcs = 3;
updateOomAdj(app3, app2, app);
@@ -2701,9 +2797,9 @@ public class MockingOomAdjusterTests {
final int userOther = 1;
// cachedAdj1 and cachedAdj2 will be read if USE_TIERED_CACHED_ADJ is disabled. Otherwise,
- // sFirstUiCachedAdj and sFirstNonUiCachedAdj are used instead.
- final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
- final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+ // sFirstUiCachedAdj and mFirstNonUiCachedAdj are used instead.
+ final int cachedAdj1 = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
+ final int cachedAdj2 = cachedAdj1 + CACHED_APP_IMPORTANCE_LEVELS * 2;
doReturn(userOwner).when(mService.mUserController).getCurrentUserId();
final ArrayList<ProcessRecord> lru = mService.mProcessList.getLruProcessesLOSP();
@@ -2723,11 +2819,11 @@ public class MockingOomAdjusterTests {
ServiceRecord s = spy(new ServiceRecord(mService, cn, cn, null, 0, null,
si, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
- app.mServices.startService(s);
- app.mState.setHasShownUi(true);
+ mProcessStateController.startService(app.mServices, s);
+ mProcessStateController.setHasShownUi(app, true);
final ServiceInfo si2 = mock(ServiceInfo.class);
si2.applicationInfo = mock(ApplicationInfo.class);
@@ -2735,26 +2831,27 @@ public class MockingOomAdjusterTests {
ServiceRecord s2 = spy(new ServiceRecord(mService, cn2, cn2, null, 0, null,
si2, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s2).getConnections();
- s2.startRequested = true;
- s2.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ mProcessStateController.setStartRequested(s2, true);
+ mProcessStateController.setServiceLastActivityTime(s2,
+ now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1);
- app2.mServices.startService(s2);
- app2.mState.setHasShownUi(false);
+ mProcessStateController.startService(app2.mServices, s2);
+ mProcessStateController.setHasShownUi(app2, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE,
mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstUiCachedAdj : cachedAdj1,
SCHED_GROUP_BACKGROUND, "cch-started-ui-services", true);
assertProcStates(app2, PROCESS_STATE_SERVICE,
- mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj2,
+ mService.mConstants.USE_TIERED_CACHED_ADJ ? mFirstNonUiCachedAdj : cachedAdj2,
SCHED_GROUP_BACKGROUND, "cch-started-services", true);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- app.mState.setHasShownUi(false);
+ mProcessStateController.setHasShownUi(app, false);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
@@ -2763,46 +2860,48 @@ public class MockingOomAdjusterTests {
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- s.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ mProcessStateController.setServiceLastActivityTime(s,
+ now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE,
- mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+ mService.mConstants.USE_TIERED_CACHED_ADJ ? mFirstNonUiCachedAdj : cachedAdj1,
SCHED_GROUP_BACKGROUND, "cch-started-services", true);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- app.mState.setHasShownUi(true);
+ mProcessStateController.setHasShownUi(app, true);
mService.mConstants.KEEP_WARMING_SERVICES.add(cn);
mService.mConstants.KEEP_WARMING_SERVICES.add(cn2);
s = spy(new ServiceRecord(mService, cn, cn, null, 0, null,
si, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
- app.mServices.startService(s);
+ mProcessStateController.startService(app.mServices, s);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
"started-services", false);
assertProcStates(app2, PROCESS_STATE_SERVICE,
- mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+ mService.mConstants.USE_TIERED_CACHED_ADJ ? mFirstNonUiCachedAdj : cachedAdj1,
SCHED_GROUP_BACKGROUND, "cch-started-services", true);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- app.mState.setHasShownUi(false);
- s.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ mProcessStateController.setHasShownUi(app, false);
+ mProcessStateController.setServiceLastActivityTime(s,
+ now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
"started-services", false);
assertProcStates(app2, PROCESS_STATE_SERVICE,
- mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+ mService.mConstants.USE_TIERED_CACHED_ADJ ? mFirstNonUiCachedAdj : cachedAdj1,
SCHED_GROUP_BACKGROUND, "cch-started-services", true);
doReturn(userOther).when(mService.mUserController).getCurrentUserId();
@@ -2810,7 +2909,7 @@ public class MockingOomAdjusterTests {
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE,
- mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+ mService.mConstants.USE_TIERED_CACHED_ADJ ? mFirstNonUiCachedAdj : cachedAdj1,
SCHED_GROUP_BACKGROUND, "cch-started-services", true);
assertProcStates(app2, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
"started-services", false);
@@ -2825,7 +2924,7 @@ public class MockingOomAdjusterTests {
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -2833,7 +2932,7 @@ public class MockingOomAdjusterTests {
// Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
// verify that its OOM adjustment level is unaffected.
bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
assertTrue(app.mServices.hasAboveClient());
updateOomAdj(app);
@@ -2847,7 +2946,7 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -2855,7 +2954,7 @@ public class MockingOomAdjusterTests {
// Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
// verify that its OOM adjustment level is unaffected.
bindService(app, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
assertFalse(app.mServices.hasAboveClient());
updateOomAdj(app);
@@ -2873,14 +2972,14 @@ public class MockingOomAdjusterTests {
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
long now = SystemClock.uptimeMillis();
ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class));
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
s = bindService(app2, app3, null, null, 0, mock(IBinder.class));
- s.lastActivity = now;
+ mProcessStateController.setServiceLastActivityTime(s, now);
s = bindService(app3, app2, null, null, 0, mock(IBinder.class));
- s.lastActivity = now;
+ mProcessStateController.setServiceLastActivityTime(s, now);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
mService.mOomAdjuster.mNumServiceProcs = 3;
updateOomAdj(app, app2, app3);
@@ -2898,19 +2997,19 @@ public class MockingOomAdjusterTests {
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
// Start binding to a service that isn't running yet.
ServiceRecord sr = makeServiceRecord(app);
- sr.app = null;
+ mProcessStateController.setHostProcess(sr, null);
bindService(null, app, null, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
// Since sr.app is null, this service cannot be in the same process as the
// client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
updateOomAdj(app);
assertTrue(app.mServices.hasAboveClient());
assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -2924,13 +3023,13 @@ public class MockingOomAdjusterTests {
setProcessesToLru(app);
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdj();
// isolated process should be killed immediately after service stop.
verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -2944,13 +3043,13 @@ public class MockingOomAdjusterTests {
MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdjPending(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdjPending(app);
// isolated process should be killed immediately after service stop.
verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -2966,13 +3065,13 @@ public class MockingOomAdjusterTests {
setProcessesToLru(app);
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdj();
// isolated process with entry point should not be killed
verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -2987,13 +3086,13 @@ public class MockingOomAdjusterTests {
app.setIsolatedEntryPoint("test");
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdjPending(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdjPending(app);
// isolated process with entry point should not be killed
verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -3014,10 +3113,10 @@ public class MockingOomAdjusterTests {
setProcessesToLru(sandboxService, client, attributedClient);
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- attributedClient.mServices.setHasForegroundServices(true, 0, true);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasForegroundServices(attributedClient.mServices, true, 0, true);
bindService(sandboxService, client, attributedClient, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(client, PROCESS_STATE_PERSISTENT, PERSISTENT_PROC_ADJ,
SCHED_GROUP_DEFAULT);
@@ -3038,16 +3137,16 @@ public class MockingOomAdjusterTests {
// App1 binds to app2 and gets temp allowlisted.
bindService(app2, app, null, null, 0, mock(IBinder.class));
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
assertEquals(true, app.getUidRecord().isSetAllowListed());
- assertEquals(true, app.mOptRecord.shouldNotFreeze());
- assertEquals(true, app2.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, false);
+ assertFreezeState(app2, false);
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
- assertEquals(false, app.mOptRecord.shouldNotFreeze());
- assertEquals(false, app2.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, true);
+ assertFreezeState(app2, true);
}
@SuppressWarnings("GuardedBy")
@@ -3064,30 +3163,30 @@ public class MockingOomAdjusterTests {
// App1 and app2 both bind to app3 and get temp allowlisted.
bindService(app3, app, null, null, 0, mock(IBinder.class));
bindService(app3, app2, null, null, 0, mock(IBinder.class));
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP2_UID, true);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, true);
assertEquals(true, app.getUidRecord().isSetAllowListed());
assertEquals(true, app2.getUidRecord().isSetAllowListed());
- assertEquals(true, app.mOptRecord.shouldNotFreeze());
- assertEquals(true, app2.mOptRecord.shouldNotFreeze());
- assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, false);
+ assertFreezeState(app2, false);
+ assertFreezeState(app3, false);
// Remove app1 from allowlist.
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(true, app2.getUidRecord().isSetAllowListed());
- assertEquals(false, app.mOptRecord.shouldNotFreeze());
- assertEquals(true, app2.mOptRecord.shouldNotFreeze());
- assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, true);
+ assertFreezeState(app2, false);
+ assertFreezeState(app3, false);
// Now remove app2 from allowlist.
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(false, app2.getUidRecord().isSetAllowListed());
- assertEquals(false, app.mOptRecord.shouldNotFreeze());
- assertEquals(false, app2.mOptRecord.shouldNotFreeze());
- assertEquals(false, app3.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, true);
+ assertFreezeState(app2, true);
+ assertFreezeState(app3, true);
}
@SuppressWarnings("GuardedBy")
@@ -3098,9 +3197,9 @@ public class MockingOomAdjusterTests {
setProcessesToLru(app);
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
"started-services");
@@ -3111,10 +3210,10 @@ public class MockingOomAdjusterTests {
verify(mService.mHandler).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
- ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+ ? mFirstNonUiCachedAdj : sFirstCachedAdj;
assertProcStates(app, PROCESS_STATE_SERVICE, expectedAdj, SCHED_GROUP_BACKGROUND,
"cch-started-services");
// Follow up should not have been called again.
@@ -3131,11 +3230,12 @@ public class MockingOomAdjusterTests {
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
app1.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
app2.mProviders.setLastProviderTime(SystemClock.uptimeMillis() + 2000);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(app1, app2);
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
- assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+ PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
SCHED_GROUP_BACKGROUND, "recent-provider");
assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND, "recent-provider");
@@ -3146,21 +3246,39 @@ public class MockingOomAdjusterTests {
verify(mService.mHandler, atLeastOnce()).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
- ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+ ? mFirstNonUiCachedAdj : sFirstCachedAdj;
assertProcStates(app1, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
"cch-empty");
verify(mService.mHandler, atLeastOnce()).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
assertProcStates(app2, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
"cch-empty");
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_FIX_APPLY_OOMADJ_ORDER)
+ public void testUpdateOomAdj_ApplyOomAdjInCorrectOrder() {
+ final int numberOfApps = 5;
+ final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
+ for (int i = 0; i < numberOfApps; i++) {
+ apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
+ MOCKAPP_PROCESSNAME + i, MOCKAPP_PACKAGENAME + i, true));
+ }
+ updateOomAdj(apps);
+ for (int i = 1; i < numberOfApps; i++) {
+ final int pre = mInjector.mSetOomAdjAppliedAt.get(apps[i - 1].mPid);
+ final int cur = mInjector.mSetOomAdjAppliedAt.get(apps[i].mPid);
+ assertTrue("setOomAdj is called in wrong order", pre < cur);
+ }
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
return new ProcessRecordBuilder(pid, uid, processName, packageName).setHasShownUi(
@@ -3169,12 +3287,12 @@ public class MockingOomAdjusterTests {
private ServiceRecord makeServiceRecord(ProcessRecord app) {
final ServiceRecord record = mock(ServiceRecord.class);
- record.app = app;
+ mProcessStateController.setHostProcess(record, app);
setFieldValue(ServiceRecord.class, record, "connections",
new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
doCallRealMethod().when(record).getConnections();
setFieldValue(ServiceRecord.class, record, "packageName", app.info.packageName);
- app.mServices.startService(record);
+ mProcessStateController.startService(app.mServices, record);
record.appInfo = app.info;
setFieldValue(ServiceRecord.class, record, "bindings", new ArrayMap<>());
setFieldValue(ServiceRecord.class, record, "pendingStarts", new ArrayList<>());
@@ -3213,27 +3331,55 @@ public class MockingOomAdjusterTests {
doCallRealMethod().when(record).addConnection(any(IBinder.class),
any(ConnectionRecord.class));
record.addConnection(binder, cr);
- client.mServices.addConnection(cr);
+ mProcessStateController.addConnection(client.mServices, cr);
binding.connections.add(cr);
doNothing().when(cr).trackProcState(anyInt(), anyInt());
return record;
}
- private ContentProviderRecord bindProvider(ProcessRecord publisher, ProcessRecord client,
- ContentProviderRecord record, String name, boolean hasExternalProviders) {
- if (record == null) {
- record = mock(ContentProviderRecord.class);
- publisher.mProviders.installProvider(name, record);
- record.proc = publisher;
- setFieldValue(ContentProviderRecord.class, record, "connections",
- new ArrayList<ContentProviderConnection>());
- doReturn(hasExternalProviders).when(record).hasExternalProcessHandles();
+ private void setWakefulness(int state) {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ mProcessStateController.setWakefulness(state);
+ } else {
+ mService.mWakefulness.set(state);
}
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void setBackupTarget(ProcessRecord app) {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ mProcessStateController.setBackupTarget(app, app.userId);
+ } else {
+ BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0, true);
+ backupTarget.app = app;
+ doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
+ }
+ }
+
+ private ContentProviderRecord createContentProviderRecord(ProcessRecord publisher, String name,
+ boolean hasExternalProviders) {
+ ContentProviderRecord record = mock(ContentProviderRecord.class);
+ mProcessStateController.addPublishedProvider(publisher, name, record);
+ record.proc = publisher;
+ setFieldValue(ContentProviderRecord.class, record, "connections",
+ new ArrayList<ContentProviderConnection>());
+ doReturn(hasExternalProviders).when(record).hasExternalProcessHandles();
+ return record;
+ }
+
+ private ContentProviderConnection bindProvider(ProcessRecord client,
+ ContentProviderRecord record) {
ContentProviderConnection conn = spy(new ContentProviderConnection(record, client,
client.info.packageName, UserHandle.getUserId(client.uid)));
record.connections.add(conn);
- client.mProviders.addProviderConnection(conn);
- return record;
+ mProcessStateController.addProviderConnection(client, conn);
+ return conn;
+ }
+
+ private void unbindProvider(ProcessRecord client, ContentProviderRecord record,
+ ContentProviderConnection conn) {
+ record.connections.remove(conn);
+ mProcessStateController.removeProviderConnection(client, conn);
}
@SuppressWarnings("GuardedBy")
@@ -3272,6 +3418,14 @@ public class MockingOomAdjusterTests {
assertEquals(expectedCached, state.isCached());
}
+ @SuppressWarnings("GuardedBy")
+ private void assertFreezeState(ProcessRecord app, boolean expectedFreezeState) {
+ boolean actualFreezeState = mTestCachedAppOptimizer.mLastSetFreezeState.get(app.getPid(),
+ false);
+ assertEquals("Unexcepted freeze state for " + app.processName, expectedFreezeState,
+ actualFreezeState);
+ }
+
private class ProcessRecordBuilder {
@SuppressWarnings("UnusedVariable")
int mPid;
@@ -3405,28 +3559,67 @@ public class MockingOomAdjusterTests {
}
providers.setLastProviderTime(mLastProviderTime);
- UidRecord uidRec = mService.mOomAdjuster.mActiveUids.get(mUid);
+ UidRecord uidRec = mActiveUids.get(mUid);
if (uidRec == null) {
uidRec = new UidRecord(mUid, mService);
- mService.mOomAdjuster.mActiveUids.put(mUid, uidRec);
+ mActiveUids.put(mUid, uidRec);
}
uidRec.addProcess(app);
app.setUidRecord(uidRec);
return app;
}
}
+ private static final class TestProcessDependencies
+ implements CachedAppOptimizer.ProcessDependencies {
+ @Override
+ public long[] getRss(int pid) {
+ return new long[]{/*totalRSS*/ 0, /*fileRSS*/ 0, /*anonRSS*/ 0, /*swap*/ 0};
+ }
+
+ @Override
+ public void performCompaction(CachedAppOptimizer.CompactProfile action, int pid) {}
+ }
+
+ private static class TestCachedAppOptimizer extends CachedAppOptimizer {
+ private SparseBooleanArray mLastSetFreezeState = new SparseBooleanArray();
+
+ TestCachedAppOptimizer(ActivityManagerService ams) {
+ super(ams, null, new TestProcessDependencies());
+ }
+
+ @Override
+ public boolean useFreezer() {
+ return true;
+ }
+
+ @Override
+ public void freezeAppAsyncLSP(ProcessRecord app) {
+ mLastSetFreezeState.put(app.getPid(), true);
+ }
+
+ @Override
+ public void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
+ mLastSetFreezeState.put(app.getPid(), false);
+ }
+ }
static class OomAdjusterInjector extends OomAdjuster.Injector {
// Jump ahead in time by this offset amount.
long mTimeOffsetMillis = 0;
private SparseIntArray mLastSetOomAdj = new SparseIntArray();
+ // A sequence number that increases every time setOomAdj is called
+ int mLastAppliedAt = 0;
+ // Holds the last sequence number setOomAdj is called for a pid
+ private SparseIntArray mSetOomAdjAppliedAt = new SparseIntArray();
+
void reset() {
mTimeOffsetMillis = 0;
mLastSetOomAdj.clear();
+ mLastAppliedAt = 0;
+ mSetOomAdjAppliedAt.clear();
}
-
void jumpUptimeAheadTo(long uptimeMillis) {
final long jumpMs = uptimeMillis - getUptimeMillis();
if (jumpMs <= 0) return;
@@ -3449,6 +3642,7 @@ public class MockingOomAdjusterTests {
final int pid = proc.getPid();
if (pid <= 0) continue;
mLastSetOomAdj.put(pid, proc.mState.getCurAdj());
+ mSetOomAdjAppliedAt.put(pid, mLastAppliedAt++);
}
}
@@ -3456,6 +3650,7 @@ public class MockingOomAdjusterTests {
void setOomAdj(int pid, int uid, int adj) {
if (pid <= 0) return;
mLastSetOomAdj.put(pid, adj);
+ mSetOomAdjAppliedAt.put(pid, mLastAppliedAt++);
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 014b98c64c66..43becc59c3cb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -216,7 +216,7 @@ public class ProcessObserverTest {
any(), any(), any(),
any(), any(),
any(), any(),
- any(), any(),
+ any(), any(), any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
r.setPid(myPid());
@@ -265,7 +265,7 @@ public class ProcessObserverTest {
null, null,
null,
null, null, null,
- null, null, null,
+ null, null, null, null,
0, 0);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 1ff4a27cb787..2988c77703b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -22,6 +22,7 @@ import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_HOME;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.content.Context.BIND_ALLOW_OOM_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
import static android.content.Context.BIND_WAIVE_PRIORITY;
@@ -29,6 +30,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEM
import static android.os.UserHandle.USER_SYSTEM;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.am.ProcessCachedOptimizerRecord.SHOULD_NOT_FREEZE_REASON_NONE;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.HOME_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
@@ -50,7 +52,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -64,6 +65,10 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -115,6 +120,8 @@ public final class ServiceBindingOomAdjPolicyTest {
public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private Context mContext;
private HandlerThread mHandlerThread;
@@ -169,6 +176,7 @@ public final class ServiceBindingOomAdjPolicyTest {
realAtm.initialize(null, null, mContext.getMainLooper());
realAms.mActivityTaskManager = spy(realAtm);
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mProcessStateController = spy(realAms.mProcessStateController);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
realAms.mPackageManagerInt = mPackageManagerInt;
@@ -242,14 +250,14 @@ public final class ServiceBindingOomAdjPolicyTest {
USER_SYSTEM // userId
));
- verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, bindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
// Unbind the service.
mAms.unbindService(serviceConnection);
- verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, unbindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
removeProcessRecord(app);
}
@@ -317,6 +325,256 @@ public final class ServiceBindingOomAdjPolicyTest {
}
@Test
+ @RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
+ public void testServiceDistinctBindingOomAdjShouldNotFreeze() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the shouldNotFreeze state needs to be propagated.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ (app) -> {
+ this.setHasForegroundServices(app);
+ this.setAllowListed(app);
+ },
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be at least 1 oom adj update
+ // because the shouldNotFreeze state needs to be propagated.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ (app) -> {
+ this.setHomeProcess(app);
+ this.setAllowListed(app);
+ },
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important (regardless of shouldNotFreeze state).
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ (app) -> {
+ this.setHasForegroundServices(app);
+ this.setAllowListed(app);
+ },
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ (app) -> {
+ this.setHomeProcess(app);
+ this.setAllowListed(app);
+ },
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be 0 oom adj update for binding
+ // because setShouldNotFreeze is already set
+ // but for the unbinding must update in case the binding could be the source of the
+ // shouldNotFreeze.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ (app) -> {
+ this.setHomeProcess(app);
+ this.setAllowListed(app);
+ },
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ (app) -> {
+ this.setHasForegroundServices(app);
+ this.setAllowListed(app);
+ },
+ BIND_AUTO_CREATE,
+ never(), atLeastOnce());
+
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ (app) -> {
+ this.setHasForegroundServices(app);
+ this.setAllowListed(app);
+ },
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
+ public void testServiceDistinctBindingOomAdjAllowOomManagement() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because BIND_ALLOW_OOM_MANAGEMENT sets the shouldNotFreeze state which needs to be
+ // propagated.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE | BIND_ALLOW_OOM_MANAGEMENT,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be at least 1 oom adj update
+ // because BIND_ALLOW_OOM_MANAGEMENT sets the shouldNotFreeze state which needs to be
+ // propagated.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE | BIND_ALLOW_OOM_MANAGEMENT,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important (regardless of BIND_ALLOW_OOM_MANAGEMENT).
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ (app) -> {
+ this.setHomeProcess(app);
+ this.setAllowListed(app);
+ },
+ BIND_AUTO_CREATE | BIND_ALLOW_OOM_MANAGEMENT,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be 0 oom adj update for binding
+ // because setShouldNotFreeze is already set
+ // but for the unbinding must update in case the BIND_ALLOW_OOM_MANAGEMENT maintaining the
+ // shouldNotFreeze.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ (app) -> {
+ this.setHasForegroundServices(app);
+ this.setAllowListed(app);
+ },
+ BIND_AUTO_CREATE | BIND_ALLOW_OOM_MANAGEMENT,
+ never(), atLeastOnce());
+
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE | BIND_ALLOW_OOM_MANAGEMENT,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
+ public void testServiceDistinctBindingOomAdjWaivePriority_propagateUnfreeze() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because BIND_WAIVE_PRIORITY sets the shouldNotFreeze state which needs to be propagated.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be at least 1 oom adj update
+ // because BIND_WAIVE_PRIORITY sets the shouldNotFreeze state which needs to be propagated.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ atLeastOnce(), atLeastOnce());
+
+ // Verify that there should be 0 oom adj update for binding
+ // because setShouldNotFreeze is already set
+ // but for the unbinding, because client is better than service, we can't skip it safely.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ (app) -> {
+ this.setHomeProcess(app);
+ this.setAllowListed(app);
+ },
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ never(), atLeastOnce());
+
+ // Verify that there should be 0 oom adj update for binding
+ // because setShouldNotFreeze is already set
+ // but for the unbinding must update in case the BIND_WAIVE_PRIORITY maintaining the
+ // shouldNotFreeze.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ (app) -> {
+ this.setHasForegroundServices(app);
+ this.setAllowListed(app);
+ },
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ never(), atLeastOnce());
+
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
public void testServiceDistinctBindingOomAdjWaivePriority() throws Exception {
// Enable the flags.
mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
@@ -496,8 +754,8 @@ public final class ServiceBindingOomAdjPolicyTest {
USER_SYSTEM // userId
));
- verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, bindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
if (clientApp.isFreezable()) {
verify(mAms.mOomAdjuster.mCachedAppOptimizer,
@@ -509,8 +767,8 @@ public final class ServiceBindingOomAdjPolicyTest {
// Unbind the service.
mAms.unbindService(serviceConnection);
- verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, unbindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
removeProcessRecord(clientApp);
removeProcessRecord(serviceApp);
@@ -527,6 +785,15 @@ public final class ServiceBindingOomAdjPolicyTest {
}
@SuppressWarnings("GuardedBy")
+ private void setAllowListed(ProcessRecord app) {
+ final UidRecord uidRec = mock(UidRecord.class);
+ app.setUidRecord(uidRec);
+ doReturn(true).when(uidRec).isCurAllowListed();
+
+ app.mOptRecord.setShouldNotFreeze(true, SHOULD_NOT_FREEZE_REASON_NONE, 1234);
+ }
+
+ @SuppressWarnings("GuardedBy")
private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap,
String packageName) {
final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
@@ -574,8 +841,10 @@ public final class ServiceBindingOomAdjPolicyTest {
String processName, String packageName, ActivityManagerService ams) {
final ProcessRecord app = ApplicationExitInfoTest.makeProcessRecord(pid, uid, packageUid,
definingUid, connectionGroup, procState, pss, rss, processName, packageName, ams);
+ app.mState.setCurRawProcState(procState);
app.mState.setCurProcState(procState);
app.mState.setSetProcState(procState);
+ app.mState.setCurRawAdj(adj);
app.mState.setCurAdj(adj);
app.mState.setSetAdj(adj);
app.mState.setCurCapability(cap);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index c77ab0f303d9..745424836faa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -21,10 +21,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static org.mockito.Mockito.verify;
+
import android.content.ContentResolver;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -34,6 +38,7 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -111,6 +116,20 @@ public class SettingsToPropertiesMapperTest {
}
@Test
+ public void testClearAconfigStorageOverride() {
+ SettingsToPropertiesMapper spyMapper = Mockito.spy(new SettingsToPropertiesMapper(
+ mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {}));
+ HashMap flags = new HashMap();
+ flags.put("test_package.test_flag", null);
+ DeviceConfig.Properties props = new DeviceConfig.Properties("test_namespace", flags);
+
+ spyMapper.setLocalOverridesInNewStorage(props);
+
+ verify(spyMapper).writeFlagOverrideRemovalRequest(new ProtoOutputStream(),
+ "test_package", "test_flag", true);
+ }
+
+ @Test
public void validateRegisteredGlobalSettings() {
HashSet<String> hashSet = new HashSet<>();
for (String globalSetting : SettingsToPropertiesMapper.sGlobalSettings) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 197342874b2a..f7c2e8b72d6b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -21,6 +21,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static org.junit.Assert.assertEquals;
+import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.os.Handler;
@@ -63,6 +64,7 @@ public class AppOpsLegacyRestrictionsTest {
@Before
public void setUp() {
+ PropertyInvalidatedCache.disableForTestMode();
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
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 1731590be3c9..026e72f117b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -31,12 +31,14 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
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;
+import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -325,6 +327,10 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.microphoneCapability()
@@ -342,10 +348,23 @@ public class AppOpsUidStateTrackerTest {
.microphoneCapability()
.update();
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.update();
+ if (delayUidStateChangesFromCapabilityUpdates()) {
+ mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+ MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ }
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED,
mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
@@ -357,6 +376,8 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.cameraCapability()
@@ -372,10 +393,18 @@ public class AppOpsUidStateTrackerTest {
.cameraCapability()
.update();
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.update();
+ if (delayUidStateChangesFromCapabilityUpdates()) {
+ mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ }
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
}
@@ -385,6 +414,9 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.locationCapability()
@@ -401,15 +433,55 @@ public class AppOpsUidStateTrackerTest {
.locationCapability()
.update();
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.update();
+ if (delayUidStateChangesFromCapabilityUpdates()) {
+ mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ }
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
}
@Test
+ public void testProcStateChangesAndStaysUnrestrictedAndCapabilityRemoved() {
+ assumeTrue(delayUidStateChangesFromCapabilityUpdates());
+
+ procStateBuilder(UID)
+ .topState()
+ .microphoneCapability()
+ .cameraCapability()
+ .locationCapability()
+ .update();
+
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+
+ procStateBuilder(UID)
+ .foregroundState()
+ .update();
+
+ mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ }
+
+ @Test
public void testVisibleAppWidget() {
procStateBuilder(UID)
.backgroundState()
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
new file mode 100644
index 000000000000..8aaa72339c5b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
@@ -0,0 +1,475 @@
+/*
+ * 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.backup;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.backup.internal.LifecycleOperationStorage;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAgentConnectionManagerTest {
+ private static final String TEST_PACKAGE = "com.test.package";
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ IActivityManager mActivityManager;
+ @Mock
+ ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ LifecycleOperationStorage mOperationStorage;
+ @Mock
+ UserBackupManagerService mUserBackupManagerService;
+ @Mock
+ IBackupAgent.Stub mBackupAgentStub;
+ @Mock
+ PackageManager mPackageManager;
+
+ private BackupAgentConnectionManager mConnectionManager;
+ private MockitoSession mSession;
+ private ApplicationInfo mTestApplicationInfo;
+ private IBackupAgent mBackupAgentResult;
+ private Thread mTestThread;
+
+ @Before
+ public void setUp() throws Exception {
+ mSession = mockitoSession().initMocks(this).mockStatic(ActivityManager.class).mockStatic(
+ LocalServices.class).strictness(Strictness.LENIENT).startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mActivityManager).when(ActivityManager::getService);
+ doReturn(mActivityManagerInternal).when(
+ () -> LocalServices.getService(ActivityManagerInternal.class));
+ // Real package manager throws if a property is not defined.
+ when(mPackageManager.getPropertyAsUser(any(), any(), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+
+ mConnectionManager = spy(
+ new BackupAgentConnectionManager(mOperationStorage, mPackageManager,
+ mUserBackupManagerService, UserHandle.USER_SYSTEM));
+
+ mTestApplicationInfo = new ApplicationInfo();
+ mTestApplicationInfo.packageName = TEST_PACKAGE;
+ mTestApplicationInfo.processName = TEST_PACKAGE;
+ mTestApplicationInfo.uid = Process.FIRST_APPLICATION_UID + 1;
+
+ mBackupAgentResult = null;
+ mTestThread = null;
+ }
+
+ @After
+ public void tearDown() {
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void bindToAgentSynchronous_amReturnsFailure_returnsNullAndClearsPendingBackups()
+ throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(false);
+
+ IBackupAgent result = mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ assertThat(result).isNull();
+ verify(mActivityManagerInternal).clearPendingBackup(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void bindToAgentSynchronous_agentDisconnectedCalled_returnsNullAndClearsPendingBackups()
+ throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(true);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)),
+ "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ mConnectionManager.agentDisconnected(TEST_PACKAGE);
+ testThread.join();
+
+ assertThat(mBackupAgentResult).isNull();
+ verify(mActivityManagerInternal).clearPendingBackup(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void bindToAgentSynchronous_agentConnectedCalled_returnsBackupAgent() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_FULL);
+
+ assertThat(mBackupAgentResult).isEqualTo(mBackupAgentStub);
+ verify(mActivityManagerInternal, never()).clearPendingBackup(anyInt());
+ }
+
+ @Test
+ public void bindToAgentSynchronous_unexpectedAgentConnected_doesNotReturnWrongAgent()
+ throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(true);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)),
+ "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ mConnectionManager.agentConnected("com.other.package", mBackupAgentStub);
+ testThread.join(100); // Avoid waiting the full timeout.
+
+ assertThat(mBackupAgentResult).isNull();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
+ throws Exception {
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode()
+ throws Exception {
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode()
+ throws Exception {
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode() throws Exception {
+ reset(mPackageManager);
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true,
+ TEST_PACKAGE, /* className= */ null));
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false,
+ TEST_PACKAGE, /* className= */ null));
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @DisableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode() throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+ mConnectionManager.clearNoRestrictedModePackages();
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+ mConnectionManager.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE);
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+ mConnectionManager.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP);
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ public void agentDisconnected_cancelsCurrentOperations() throws Exception {
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+ when(mOperationStorage.operationTokensForPackage(eq(TEST_PACKAGE))).thenReturn(
+ ImmutableSet.of(123, 456, 789));
+ when(mConnectionManager.getThreadForCancellation(any())).thenAnswer(invocation -> {
+ Thread testThread = new Thread((Runnable) invocation.getArgument(0),
+ "agent-disconnected-test");
+ setTestThread(testThread);
+ return testThread;
+ });
+
+ mConnectionManager.agentDisconnected(TEST_PACKAGE);
+
+ mTestThread.join();
+ verify(mUserBackupManagerService).handleCancel(eq(123), eq(true));
+ verify(mUserBackupManagerService).handleCancel(eq(456), eq(true));
+ verify(mUserBackupManagerService).handleCancel(eq(789), eq(true));
+ }
+
+ @Test
+ public void unbindAgent_callsAmUnbindBackupAgent() throws Exception {
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ false);
+
+ verify(mActivityManager).unbindBackupAgent(eq(mTestApplicationInfo));
+ }
+
+ @Test
+ public void unbindAgent_doNotAllowKill_doesNotKillApp() throws Exception {
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ false);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_isCoreApp_doesNotKillApp() throws Exception {
+ mTestApplicationInfo.uid = 1000;
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_notCurrentConnection_killsApp() throws Exception {
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_inRestrictedMode_killsApp() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_FULL);
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_notInRestrictedMode_doesNotKillApp() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_isRestore_noKillAfterRestore_doesNotKillApp()
+ throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_RESTORE);
+ mTestApplicationInfo.flags = 0;
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_isRestore_killAfterRestore_killsApp() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_RESTORE);
+ mTestApplicationInfo.flags |= ApplicationInfo.FLAG_KILL_AFTER_RESTORE;
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+ }
+
+ // Needed because variables can't be assigned directly inside lambdas in Java.
+ private void setBackupAgentResult(IBackupAgent result) {
+ mBackupAgentResult = result;
+ }
+
+ // Needed because variables can't be assigned directly inside lambdas in Java.
+ private void setTestThread(Thread thread) {
+ mTestThread = thread;
+ }
+
+ private void bindAndConnectToTestAppAgent(int backupMode) throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(true);
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, backupMode,
+ BackupDestination.CLOUD)), "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub);
+ testThread.join();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 65286d9aabc7..7a9e96f1bc4c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -21,38 +21,38 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.IActivityManager;
import android.app.backup.BackupAgent;
-import android.app.backup.BackupAnnotations;
import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.testing.TestableContext;
import android.util.FeatureFlagUtils;
import android.util.KeyValueListParser;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.internal.BackupHandler;
@@ -64,11 +64,11 @@ import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.BackupManagerMonitorEventSender;
-import com.google.common.collect.ImmutableSet;
-
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -77,7 +77,6 @@ import org.mockito.quality.Strictness;
import java.util.Arrays;
import java.util.List;
-import java.util.function.IntConsumer;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -85,9 +84,13 @@ public class UserBackupManagerServiceTest {
private static final String TEST_PACKAGE = "package1";
private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
private static final String TEST_TRANSPORT = "transport";
- private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
@UserIdInt private static final int USER_ID = 0;
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock IBackupManagerMonitor mBackupManagerMonitor;
@Mock IBackupObserver mBackupObserver;
@Mock PackageManager mPackageManager;
@@ -99,10 +102,14 @@ public class UserBackupManagerServiceTest {
@Mock JobScheduler mJobScheduler;
@Mock BackupHandler mBackupHandler;
@Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
+ @Mock IActivityManager mActivityManager;
+ @Mock
+ ActivityManagerInternal mActivityManagerInternal;
private TestableContext mContext;
private MockitoSession mSession;
private TestBackupService mService;
+ private ApplicationInfo mTestPackageApplicationInfo;
@Before
public void setUp() throws Exception {
@@ -120,12 +127,14 @@ public class UserBackupManagerServiceTest {
mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP,
PackageManager.PERMISSION_GRANTED);
- mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
- mTransportManager, mBackupHandler);
+ mService = new TestBackupService();
mService.setEnabled(true);
mService.setSetupComplete(true);
mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0);
- }
+
+ mTestPackageApplicationInfo = new ApplicationInfo();
+ mTestPackageApplicationInfo.packageName = TEST_PACKAGE;
+ }
@After
public void tearDown() {
@@ -256,33 +265,6 @@ public class UserBackupManagerServiceTest {
}
@Test
- @FlakyTest
- public void testAgentDisconnected_cancelsCurrentOperations() throws Exception {
- when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
- ImmutableSet.of(123, 456, 789)
- );
-
- mService.agentDisconnected("com.android.foo");
-
- mService.waitForAsyncOperation();
- verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class));
- verify(mOperationStorage).cancelOperation(eq(456), eq(true), any());
- verify(mOperationStorage).cancelOperation(eq(789), eq(true), any());
- }
-
- @Test
- public void testAgentDisconnected_unknownPackageName_cancelsNothing() throws Exception {
- when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
- ImmutableSet.of()
- );
-
- mService.agentDisconnected("com.android.foo");
-
- verify(mOperationStorage, never())
- .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class));
- }
-
- @Test
public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception {
PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE);
when(mPackageManager.getPackageInfoAsUser(anyString(),
@@ -298,9 +280,8 @@ public class UserBackupManagerServiceTest {
new DataTypeResult(/* dataType */ "type_2"));
mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
-
verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults(
- eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE));
+ eq(packageInfo), eq(results), eq(OperationType.RESTORE));
}
private static PackageInfo getPackageInfo(String packageName) {
@@ -314,13 +295,9 @@ public class UserBackupManagerServiceTest {
boolean isEnabledStatePersisted = false;
boolean shouldUseNewBackupEligibilityRules = false;
- private volatile Thread mWorkerThread = null;
-
- TestBackupService(Context context, PackageManager packageManager,
- LifecycleOperationStorage operationStorage, TransportManager transportManager,
- BackupHandler backupHandler) {
- super(context, packageManager, operationStorage, transportManager, backupHandler,
- createConstants(context));
+ TestBackupService() {
+ super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler,
+ createConstants(mContext), mActivityManager, mActivityManagerInternal);
}
private static BackupManagerConstants createConstants(Context context) {
@@ -352,26 +329,8 @@ public class UserBackupManagerServiceTest {
}
@Override
- Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
- mWorkerThread = super.getThreadForAsyncOperation(operationName, operation);
- return mWorkerThread;
- }
-
- @Override
BackupManagerMonitorEventSender getBMMEventSender(IBackupManagerMonitor monitor) {
return mBackupManagerMonitorEventSender;
}
-
- private void waitForAsyncOperation() {
- if (mWorkerThread == null) {
- return;
- }
-
- try {
- mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS);
- } catch (InterruptedException e) {
- fail("Failed waiting for worker thread to complete: " + e.getMessage());
- }
- }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
index 94742537ed1a..e618433862f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
@@ -18,34 +18,100 @@ package com.android.server.backup.fullbackup;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupAnnotations;
+import android.app.backup.BackupTransport;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.BackupAgentConnectionManager;
+import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.utils.BackupEligibilityRules;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class PerformFullTransportBackupTaskTest {
+ private static final String TEST_PACKAGE_1 = "package1";
+ private static final String TEST_PACKAGE_2 = "package2";
+
+ @Mock
+ BackupAgentTimeoutParameters mBackupAgentTimeoutParameters;
+ @Mock
+ BackupEligibilityRules mBackupEligibilityRules;
@Mock
UserBackupManagerService mBackupManagerService;
@Mock
+ BackupAgentConnectionManager mBackupAgentConnectionManager;
+ @Mock
+ BackupTransportClient mBackupTransportClient;
+ @Mock
+ CountDownLatch mLatch;
+ @Mock
+ OperationStorage mOperationStorage;
+ @Mock
+ PackageManager mPackageManager;
+ @Mock
+ TransportConnection mTransportConnection;
+ @Mock
TransportManager mTransportManager;
+ @Mock
+ UserBackupManagerService.BackupWakeLock mWakeLock;
+
+ private final List<String> mEligiblePackages = new ArrayList<>();
+
+ private PerformFullTransportBackupTask mTask;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mBackupManagerService.getPackageManager()).thenReturn(mPackageManager);
+ when(mBackupManagerService.getQueueLock()).thenReturn("something!");
+ when(mBackupManagerService.isEnabled()).thenReturn(true);
+ when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+ when(mBackupManagerService.isSetupComplete()).thenReturn(true);
+ when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(
+ mBackupAgentTimeoutParameters);
+ when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn(
+ mBackupAgentConnectionManager);
when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+ when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection);
+ when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient);
+ when(mTransportConnection.connect(any())).thenReturn(mBackupTransportClient);
+ when(mBackupTransportClient.performFullBackup(any(), any(), anyInt())).thenReturn(
+ BackupTransport.TRANSPORT_ERROR);
+ when(mBackupEligibilityRules.appIsEligibleForBackup(
+ argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+ true);
+ when(mBackupEligibilityRules.appGetsFullBackup(
+ argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+ true);
}
@Test
@@ -70,4 +136,49 @@ public class PerformFullTransportBackupTaskTest {
/* backupEligibilityRules */ null);
});
}
+
+ @Test
+ public void run_setsAndClearsNoRestrictedModePackages() throws Exception {
+ mockPackageEligibleForFullBackup(TEST_PACKAGE_1);
+ mockPackageEligibleForFullBackup(TEST_PACKAGE_2);
+ createTask(new String[] {TEST_PACKAGE_1, TEST_PACKAGE_2});
+ when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+ anyInt())).thenReturn(Set.of("package1"));
+
+ mTask.run();
+
+ InOrder inOrder = inOrder(mBackupAgentConnectionManager);
+ inOrder.verify(mBackupAgentConnectionManager).setNoRestrictedModePackages(
+ eq(Set.of("package1")),
+ eq(BackupAnnotations.OperationType.BACKUP));
+ inOrder.verify(mBackupAgentConnectionManager).clearNoRestrictedModePackages();
+ }
+
+ private void createTask(String[] packageNames) {
+ mTask = PerformFullTransportBackupTask
+ .newWithCurrentTransport(
+ mBackupManagerService,
+ mOperationStorage,
+ /* observer */ null,
+ /* whichPackages */ packageNames,
+ /* updateSchedule */ false,
+ /* runningJob */ null,
+ mLatch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ /* caller */ null,
+ mBackupEligibilityRules);
+ }
+
+ private void mockPackageEligibleForFullBackup(String packageName) throws Exception {
+ mEligiblePackages.add(packageName);
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = packageName;
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = appInfo;
+ when(mPackageManager.getPackageInfoAsUser(eq(packageName), anyInt(), anyInt())).thenReturn(
+ packageInfo);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 414532b88e22..351aac357c44 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -23,8 +23,10 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
@@ -42,6 +44,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.android.server.backup.BackupAgentConnectionManager;
import com.android.server.backup.Flags;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
@@ -91,6 +94,10 @@ public class PerformUnifiedRestoreTaskTest {
private UserBackupManagerService mBackupManagerService;
@Mock
private TransportConnection mTransportConnection;
+ @Mock
+ private BackupTransportClient mBackupTransportClient;
+ @Mock
+ private BackupAgentConnectionManager mBackupAgentConnectionManager;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -118,6 +125,9 @@ public class PerformUnifiedRestoreTaskTest {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn(
+ mBackupAgentConnectionManager);
+
mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
when(mBackupDataInput.readNextHeader())
.then((Answer<Boolean>) invocation -> !mBackupDataSource.isEmpty());
@@ -151,6 +161,23 @@ public class PerformUnifiedRestoreTaskTest {
}
@Test
+ public void setNoRestrictedModePackages_callsTransportAndSetsValue() throws Exception {
+ PackageInfo packageInfo1 = new PackageInfo();
+ packageInfo1.packageName = "package1";
+ PackageInfo packageInfo2 = new PackageInfo();
+ packageInfo2.packageName = "package2";
+ when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+ anyInt())).thenReturn(Set.of("package1"));
+
+ mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient,
+ new PackageInfo[]{packageInfo1, packageInfo2});
+
+ verify(mBackupAgentConnectionManager).setNoRestrictedModePackages(
+ eq(Set.of("package1")),
+ eq(BackupAnnotations.OperationType.RESTORE));
+ }
+
+ @Test
public void testFilterExcludedKeys() throws Exception {
when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
.thenReturn(mExcludedkeys);
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index 2d7d46f83c47..13e32078f609 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -19,7 +19,14 @@ package com.android.server.backup.transport;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
-
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
@@ -38,15 +45,31 @@ import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.ITransportStatusCallback;
import com.android.internal.infra.AndroidFuture;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.Set;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupTransportClientTest {
+ @Mock
+ IBackupTransport mMockBackupTransport;
+
+ private BackupTransportClient mMockingTransportClient;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mMockingTransportClient = new BackupTransportClient(
+ mMockBackupTransport);
+ }
+
private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase {
public final Object mLock = new Object();
@@ -128,6 +151,70 @@ public class BackupTransportClientTest {
thread.join();
}
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_passesSetAsListToBinder()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of("package1", "package2"),
+ OperationType.BACKUP);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(
+ argThat(list -> Set.copyOf(list).equals(Set.of("package1", "package2"))),
+ eq(OperationType.BACKUP), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_forRestore_callsBinderForRestore()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.RESTORE);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+ eq(OperationType.RESTORE), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_forBackup_callsBinderForBackup()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.BACKUP);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+ eq(OperationType.BACKUP), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_nullResult_returnsEmptySet()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.BACKUP);
+
+ assertThat(result).isEqualTo(Set.of());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_returnsResultAsSet()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+ Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of("package1", "package2"),
+ OperationType.BACKUP);
+
+ assertThat(result).isEqualTo(Set.of("package1", "package2"));
+ }
+
private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase {
public final Object mLock = new Object();
@@ -158,7 +245,6 @@ public class BackupTransportClientTest {
assertThat(status).isEqualTo(123);
}
-
@Test
public void testFinishBackup_completesLater_returnsStatus() throws Exception {
TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
@@ -211,6 +297,14 @@ public class BackupTransportClientTest {
thread.join();
}
+ private void mockGetPackagesThatShouldNotUseRestrictedModeReturn(List<String> returnList)
+ throws Exception {
+ doAnswer(
+ i -> ((AndroidFuture<List<String>>) i.getArguments()[2]).complete(returnList)).when(
+ mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), anyInt(),
+ any());
+ }
+
// Convenience layer so we only need to fake specific methods useful for each test case.
private static class FakeTransportBinderBase implements IBackupTransport {
@Override public void name(AndroidFuture<String> f) throws RemoteException {}
@@ -258,6 +352,10 @@ public class BackupTransportClientTest {
@Override
public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture)
throws RemoteException {}
+ @Override
+ public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+ int operationType, AndroidFuture<List<String>> resultFuture)
+ throws RemoteException {}
@Override public IBinder asBinder() {
return null;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index 7ac7aca3fd59..8eae9c7d71fa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -36,7 +36,10 @@ android_test {
"services.core",
"truth",
"flag-junit",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": ["service-crashrecovery-pre-jarjar"],
+ default: [],
+ }),
libs: [
"android.test.mock.stubs.system",
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
index c8e4f89aaee6..3b6c86e3c94f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
@@ -16,7 +16,7 @@
package com.android.server.job;
-import static android.app.job.Flags.FLAG_CLEANUP_EMPTY_JOBS;
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -129,9 +129,9 @@ public class JobParametersTest {
}
/** Test to verify that the JobParameters Cleaner is disabled */
- @RequiresFlagsEnabled(FLAG_CLEANUP_EMPTY_JOBS)
+ @RequiresFlagsEnabled(FLAG_HANDLE_ABANDONED_JOBS)
@Test
- public void testCleanerWithLeakedJobCleanerDisabled_flagCleanupEmptyJobsEnabled() {
+ public void testCleanerWithLeakedJobCleanerDisabled_flagHandleAbandonedJobs() {
// Inject real JobCallbackCleanup
JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 4e1f741b1398..c831475577d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -82,6 +83,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1056,6 +1058,75 @@ public class JobSchedulerServiceTest {
/**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
+ * returns a job with the correct delay for abandoned jobs.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testGetRescheduleJobForFailure_abandonedJob() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long initialBackoffMs = MINUTE_IN_MILLIS;
+ mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+
+ JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
+ createJobInfo()
+ .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
+ assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+
+ // failure = 1, systemStop = 0, abandoned = 1
+ JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 2, systemstop = 0, abandoned = 2
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + (2 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 3, systemstop = 0, abandoned = 3
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + (3 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemstop = 0, abandoned = 4
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemstop = 1, abandoned = 4
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemStop = 4 / SYSTEM_STOP_TO_FAILURE_RATIO, abandoned = 4
+ for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+ JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
+ }
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 4)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+ }
+
+ /**
+ * Confirm that
+ * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
* returns a job that is correctly marked as demoted by the user.
*/
@Test
@@ -2351,6 +2422,7 @@ public class JobSchedulerServiceTest {
/** Tests that jobs are removed from the pending list if the user stops the app. */
@Test
+ @RequiresFlagsDisabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
public void testUserStopRemovesPending() {
spyOn(mService);
@@ -2402,6 +2474,60 @@ public class JobSchedulerServiceTest {
assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
}
+ /** Tests that jobs are removed from the pending list if the user stops the app. */
+ @Test
+ @RequiresFlagsEnabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+ public void testUserStopRemovesPending_withPendingJobReasonsApi() {
+ spyOn(mService);
+
+ JobStatus job1a = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(1), 1, "pkg1");
+ JobStatus job1b = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(2), 1, "pkg1");
+ JobStatus job2a = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(1), 2, "pkg2");
+ JobStatus job2b = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(2), 2, "pkg2");
+ doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
+ doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
+ doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
+
+ mService.getPendingJobQueue().clear();
+ mService.getPendingJobQueue().add(job1a);
+ mService.getPendingJobQueue().add(job1b);
+ mService.getPendingJobQueue().add(job2a);
+ mService.getPendingJobQueue().add(job2b);
+ mService.getJobStore().add(job1a);
+ mService.getJobStore().add(job1b);
+ mService.getJobStore().add(job2a);
+ mService.getJobStore().add(job2b);
+
+ mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
+ assertEquals(4, mService.getPendingJobQueue().size());
+ assertTrue(mService.getPendingJobQueue().contains(job1a));
+ assertTrue(mService.getPendingJobQueue().contains(job1b));
+ assertTrue(mService.getPendingJobQueue().contains(job2a));
+ assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+ mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
+ assertEquals(2, mService.getPendingJobQueue().size());
+ assertFalse(mService.getPendingJobQueue().contains(job1a));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1a)[0]);
+ assertFalse(mService.getPendingJobQueue().contains(job1b));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1b)[0]);
+ assertTrue(mService.getPendingJobQueue().contains(job2a));
+ assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+ mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
+ assertEquals(0, mService.getPendingJobQueue().size());
+ assertFalse(mService.getPendingJobQueue().contains(job1a));
+ assertFalse(mService.getPendingJobQueue().contains(job1b));
+ assertFalse(mService.getPendingJobQueue().contains(job2a));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2a)[0]);
+ assertFalse(mService.getPendingJobQueue().contains(job2b));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2b)[0]);
+ }
+
/**
* Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link
* JobRestriction} registered.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
new file mode 100644
index 000000000000..8c66fd0e684a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job;
+
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppGlobals;
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.job.JobServiceContext.JobCallback;
+import com.android.server.job.controllers.JobStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+public class JobServiceContextTest {
+ private static final String TAG = JobServiceContextTest.class.getSimpleName();
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+ @Mock
+ private JobSchedulerService mMockJobSchedulerService;
+ @Mock
+ private JobConcurrencyManager mMockConcurrencyManager;
+ @Mock
+ private JobNotificationCoordinator mMockNotificationCoordinator;
+ @Mock
+ private IBatteryStats.Stub mMockBatteryStats;
+ @Mock
+ private JobPackageTracker mMockJobPackageTracker;
+ @Mock
+ private Looper mMockLooper;
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private JobStatus mMockJobStatus;
+ @Mock
+ private JobParameters mMockJobParameters;
+ @Mock
+ private JobCallback mMockJobCallback;
+ private MockitoSession mMockingSession;
+ private JobServiceContext mJobServiceContext;
+ private Object mLock;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession =
+ mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ doReturn(mock(PowerManager.class)).when(mMockContext).getSystemService(PowerManager.class);
+ doReturn(mMockContext).when(mMockJobSchedulerService).getContext();
+ mLock = new Object();
+ doReturn(mLock).when(mMockJobSchedulerService).getLock();
+ mJobServiceContext =
+ new JobServiceContext(
+ mMockJobSchedulerService,
+ mMockConcurrencyManager,
+ mMockNotificationCoordinator,
+ mMockBatteryStats,
+ mMockJobPackageTracker,
+ mMockLooper);
+ spyOn(mJobServiceContext);
+ mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private Clock getAdvancedClock(Clock clock, long incrementMs) {
+ return Clock.offset(clock, Duration.ofMillis(incrementMs));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock =
+ getAdvancedClock(JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+ }
+
+ /**
+ * Test that Abandoned jobs that are timed out are stopped with the correct stop reason
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_TimeoutAbandonedJob() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(true).when(mMockJobStatus).isAbandoned();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ mJobServiceContext.handleOpTimeoutLocked();
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing and maybe abandoned", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT_ABANDONED,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED,
+ "client timed out and maybe abandoned");
+ }
+
+ /**
+ * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_TimeoutNoAbandonedJob() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(false).when(mMockJobStatus).isAbandoned();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ mJobServiceContext.handleOpTimeoutLocked();
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+ "client timed out");
+ }
+
+ /**
+ * Test that abandoned jobs that are timed out while the flag is disabled
+ * are stopped with the correct stop reason
+ */
+ @Test
+ @DisableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(true).when(mMockJobStatus).isAbandoned();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ mJobServiceContext.handleOpTimeoutLocked();
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+ "client timed out");
+ }
+
+ /**
+ * Test that the JobStatus is marked as abandoned when the JobServiceContext
+ * receives a MSG_HANDLE_ABANDONED_JOB message
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_HandleAbandonedJob() {
+ final int jobId = 123;
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+ doReturn(jobId).when(mMockJobStatus).getJobId();
+
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+ verify(mMockJobStatus).setAbandoned(true);
+ }
+
+ /**
+ * Test that the JobStatus is not marked as abandoned when the
+ * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+ * JobServiceContext is not running a job
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_HandleAbandonedJob_notRunningJob() {
+ final int jobId = 123;
+ mJobServiceContext.setRunningJobLockedForTest(null);
+ mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+ verify(mMockJobStatus, never()).setAbandoned(true);
+ }
+
+ /**
+ * Test that the JobStatus is not marked as abandoned when the
+ * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+ * JobServiceContext is running a job with a different jobId
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_HandleAbandonedJob_differentJobId() {
+ final int jobId = 123;
+ final int differentJobId = 456;
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+ doReturn(differentJobId).when(mMockJobStatus).getJobId();
+
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+ verify(mMockJobStatus, never()).setAbandoned(true);
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index c6a6865f1cf1..c64973a67589 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -706,14 +706,14 @@ public class FlexibilityControllerTest {
// "True" start is nowElapsed + HOUR_IN_MILLIS
nowElapsed + HOUR_IN_MILLIS + adjustmentMs,
nowElapsed + 2 * HOUR_IN_MILLIS,
- 0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
jsFlex = new JobStatus(jsFlex,
// "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS
nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs,
nowElapsed + 2 * HOUR_IN_MILLIS,
- 0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
@@ -726,13 +726,13 @@ public class FlexibilityControllerTest {
jsBasic = new JobStatus(jsBasic,
nowElapsed + 30 * MINUTE_IN_MILLIS,
NO_LATEST_RUNTIME,
- 1 /* numFailures */, 1 /* numSystemStops */,
+ 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
jsFlex = new JobStatus(jsFlex,
nowElapsed + 30 * MINUTE_IN_MILLIS,
NO_LATEST_RUNTIME,
- 1 /* numFailures */, 1 /* numSystemStops */,
+ 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
@@ -847,21 +847,24 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 0,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 10,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
@@ -1092,11 +1095,13 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1,
+ /* numAbandonedFailures */ 0, /* numSystemStops */ 0,
0, FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+ /* numAbandonedFailures */ 0, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 2d0f4b69e2fe..86101cf591e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -459,35 +459,35 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority should be lowered as much as possible.
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// Less than 2 failures, but job is downgraded.
numFailures = 1;
numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -505,44 +505,44 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// Failures in [2,4), priority should be lowered slightly.
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
numFailures = 3;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
// Failures in [4,6), priority should be lowered more.
numFailures = 4;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
numFailures = 6;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
numFailures = 12;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -563,32 +563,32 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 4;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
numFailures = 6;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
numFailures = 12;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
}
@@ -606,28 +606,28 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority shouldn't be affected while job is still a UI job
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// Job can no long run as user-initiated. Downgrades should be effective.
@@ -641,28 +641,28 @@ public class JobStatusTest {
numFailures = 1;
numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// 2+ failures, priority should start getting lower
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -772,14 +772,14 @@ public class JobStatusTest {
assertTrue(job.shouldTreatAsUserInitiatedJob());
JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
assertFalse(job.shouldTreatAsUserInitiatedJob());
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
@@ -797,14 +797,14 @@ public class JobStatusTest {
assertTrue(job.shouldTreatAsUserInitiatedJob());
JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
assertFalse(job.shouldTreatAsUserInitiatedJob());
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 584fd6270c69..c6870adb8464 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -65,6 +65,7 @@ import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -76,9 +77,12 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -89,21 +93,24 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
import com.android.server.job.controllers.QuotaController.QcConstants;
-import com.android.server.job.controllers.QuotaController.QuotaBump;
import com.android.server.job.controllers.QuotaController.ShrinkableDebits;
import com.android.server.job.controllers.QuotaController.TimedEvent;
import com.android.server.job.controllers.QuotaController.TimingSession;
import com.android.server.usage.AppStandbyInternal;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
@@ -131,11 +138,14 @@ public class QuotaControllerTest {
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
private QuotaController mQuotaController;
private QuotaController.QcConstants mQcConstants;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
private int mSourceUid;
- private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener;
private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
private IUidObserver mUidObserver;
private UsageStatsManagerInternal.UsageEventListener mUsageEventListener;
@@ -190,8 +200,7 @@ public class QuotaControllerTest {
when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
doReturn(mActivityMangerInternal)
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
- final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class);
- doReturn(appStandbyInternal)
+ doReturn(mock(AppStandbyInternal.class))
.when(() -> LocalServices.getService(AppStandbyInternal.class));
doReturn(mock(BatteryManagerInternal.class))
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
@@ -238,8 +247,6 @@ public class QuotaControllerTest {
// Initialize real objects.
// Capture the listeners.
- ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> aiscListenerCaptor =
- ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
ArgumentCaptor<IUidObserver> uidObserverCaptor =
ArgumentCaptor.forClass(IUidObserver.class);
ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
@@ -249,8 +256,6 @@ public class QuotaControllerTest {
mQuotaController = new QuotaController(mJobSchedulerService,
mock(BackgroundJobsController.class), mock(ConnectivityController.class));
- verify(appStandbyInternal).addListener(aiscListenerCaptor.capture());
- mAppIdleStateChangeListener = aiscListenerCaptor.getValue();
verify(mPowerAllowlistInternal)
.registerTempAllowlistChangeListener(taChangeCaptor.capture());
mTempAllowlistListener = taChangeCaptor.getValue();
@@ -303,6 +308,12 @@ public class QuotaControllerTest {
}
}
+ private int getProcessStateQuotaFreeThreshold() {
+ synchronized (mQuotaController.mLock) {
+ return mQuotaController.getProcessStateQuotaFreeThreshold(mSourceUid);
+ }
+ }
+
private void setProcessState(int procState) {
setProcessState(procState, mSourceUid);
}
@@ -315,7 +326,7 @@ public class QuotaControllerTest {
final boolean contained = foregroundUids.get(uid);
mUidObserver.onUidStateChanged(uid, procState, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
if (!contained) {
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
.put(eq(uid), eq(true));
@@ -481,14 +492,12 @@ public class QuotaControllerTest {
now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
TimingSession two = createTimingSession(
now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
- QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS);
TimingSession thr = createTimingSession(
now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
// Overlaps 24 hour boundary.
TimingSession fou = createTimingSession(
now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
// Way past the 24 hour boundary.
- QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
TimingSession fiv = createTimingSession(
now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
List<TimedEvent> expectedRegular = new ArrayList<>();
@@ -496,16 +505,13 @@ public class QuotaControllerTest {
// Added in correct (chronological) order.
expectedRegular.add(fou);
expectedRegular.add(thr);
- expectedRegular.add(bump1);
expectedRegular.add(two);
expectedRegular.add(one);
expectedEJ.add(fou);
expectedEJ.add(one);
mQuotaController.saveTimingSession(0, "com.android.test", fiv, false);
- mQuotaController.getTimingSessions(0, "com.android.test").add(bump2);
mQuotaController.saveTimingSession(0, "com.android.test", fou, false);
mQuotaController.saveTimingSession(0, "com.android.test", thr, false);
- mQuotaController.getTimingSessions(0, "com.android.test").add(bump1);
mQuotaController.saveTimingSession(0, "com.android.test", two, false);
mQuotaController.saveTimingSession(0, "com.android.test", one, false);
mQuotaController.saveTimingSession(0, "com.android.test", fiv, true);
@@ -935,22 +941,26 @@ public class QuotaControllerTest {
* Tests that getExecutionStatsLocked returns the correct stats.
*/
@Test
- public void testGetExecutionStatsLocked_Values() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(0, "com.android.test",
- createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - HOUR_IN_MILLIS),
+ 10 * MINUTE_IN_MILLIS, 5), false);
mQuotaController.saveTimingSession(0, "com.android.test",
- createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ 10 * MINUTE_IN_MILLIS, 5), false);
mQuotaController.saveTimingSession(0, "com.android.test",
- createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
+ 10 * MINUTE_IN_MILLIS, 5), false);
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
ExecutionStats expectedStats = new ExecutionStats();
// Active
- expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
- expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
@@ -965,7 +975,7 @@ public class QuotaControllerTest {
}
// Working
- expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
expectedStats.expirationTimeElapsed = now;
@@ -982,7 +992,7 @@ public class QuotaControllerTest {
}
// Frequent
- expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -1000,7 +1010,7 @@ public class QuotaControllerTest {
}
// Rare
- expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -1017,11 +1027,112 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
+ expectedStats.expirationTimeElapsed = now + 14 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 5;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ // There is only one session in the past active bucket window, the empty time for this
+ // window is the bucket window size - duration of the session.
+ expectedStats.expirationTimeElapsed = now + 24 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+
+ // Working
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 10;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 2;
+ expectedStats.inQuotaTimeElapsed = now + 2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ WORKING_INDEX));
+ }
+
+ // Frequent
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 15;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 3;
+ expectedStats.inQuotaTimeElapsed = now + 10 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX));
+ }
+
+ // Rare
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 20;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ RARE_INDEX));
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
*/
@Test
- public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime_LegacyDefaultBucketWindowSizes() {
// Set time to 3 minutes after boot.
advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
advanceElapsedClock(3 * MINUTE_IN_MILLIS);
@@ -1044,7 +1155,8 @@ public class QuotaControllerTest {
expectedStats.sessionCountInWindow = 1;
synchronized (mQuotaController.mLock) {
assertEquals(expectedStats,
- mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
}
// Working
@@ -1054,7 +1166,8 @@ public class QuotaControllerTest {
expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
synchronized (mQuotaController.mLock) {
assertEquals(expectedStats,
- mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ WORKING_INDEX));
}
// Frequent
@@ -1075,7 +1188,83 @@ public class QuotaControllerTest {
expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
synchronized (mQuotaController.mLock) {
assertEquals(expectedStats,
- mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ RARE_INDEX));
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes() {
+ // Set time to 3 minutes after boot.
+ advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+ advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 10 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 2;
+ expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 2;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 20 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+
+ // Working
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+ expectedStats.expirationTimeElapsed = 4 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ WORKING_INDEX));
+ }
+
+ // Frequent
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+ expectedStats.expirationTimeElapsed = 12 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX));
+ }
+
+ // Rare
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+ expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ RARE_INDEX));
}
}
@@ -1083,7 +1272,8 @@ public class QuotaControllerTest {
* Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
*/
@Test
- public void testGetExecutionStatsLocked_CoalescingSessions() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_CoalescingSessions_LegacyDefaultBucketWindowSizes() {
for (int i = 0; i < 10; ++i) {
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(
@@ -1100,12 +1290,14 @@ public class QuotaControllerTest {
advanceElapsedClock(54 * SECOND_IN_MILLIS);
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(
- JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 500, 1), false);
advanceElapsedClock(500);
advanceElapsedClock(400);
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(
- JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 100, 1), false);
advanceElapsedClock(100);
advanceElapsedClock(5 * SECOND_IN_MILLIS);
}
@@ -1231,6 +1423,164 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes() {
+ for (int i = 0; i < 20; ++i) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 5 * MINUTE_IN_MILLIS, 5), false);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ for (int j = 0; j < 5; ++j) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ MINUTE_IN_MILLIS, 2), false);
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ advanceElapsedClock(54 * SECOND_IN_MILLIS);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
+ advanceElapsedClock(500);
+ advanceElapsedClock(400);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
+ advanceElapsedClock(100);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ }
+ advanceElapsedClock(40 * MINUTE_IN_MILLIS);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(64, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(192, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(320, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 5 TimingSessions are coalesced
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 5 TimingSessions are coalesced
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 5 TimingSessions are coalesced
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * SECOND_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 9 TimingSessions are coalesced
+ assertEquals(28, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 9 TimingSessions are coalesced
+ assertEquals(84, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 9 TimingSessions are coalesced
+ assertEquals(140, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ MINUTE_IN_MILLIS);
+
+ // Only two TimingSessions there for every hour.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(8, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(24, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(40, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * MINUTE_IN_MILLIS);
+
+ // Only one TimingSessions there for every hour
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 15 * MINUTE_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
+ // between an hour and 15 minutes.
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
*/
@@ -1245,7 +1595,8 @@ public class QuotaControllerTest {
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
mQuotaController.saveTimingSession(0, "com.android.test",
- createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
+ 10 * MINUTE_IN_MILLIS, 5), false);
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
final ExecutionStats originalStatsActive;
@@ -1348,16 +1699,9 @@ public class QuotaControllerTest {
3 * MINUTE_IN_MILLIS, 5), false);
final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS;
JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
- //noinspection deprecation
- JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked",
- createJobInfoBuilder(1)
- .setImportantWhileForeground(true)
- .setPriority(JobInfo.PRIORITY_DEFAULT)
- .build());
JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked",
createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build());
setStandbyBucket(RARE_INDEX, job);
- setStandbyBucket(RARE_INDEX, jobDefIWF);
setStandbyBucket(RARE_INDEX, jobHigh);
setCharging();
@@ -1365,28 +1709,25 @@ public class QuotaControllerTest {
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
- mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
- assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
- mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
- assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
}
// Top-started job
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-stared jobs are out of quota enforcement.
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
- trackJobs(job, jobDefIWF, jobHigh);
+ trackJobs(job, jobHigh);
mQuotaController.prepareForExecutionLocked(job);
- mQuotaController.prepareForExecutionLocked(jobDefIWF);
mQuotaController.prepareForExecutionLocked(jobHigh);
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
@@ -1394,11 +1735,8 @@ public class QuotaControllerTest {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
- mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
- assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
mQuotaController.maybeStopTrackingJobLocked(job, null);
- mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
}
@@ -1407,13 +1745,171 @@ public class QuotaControllerTest {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
assertEquals(timeUntilQuotaConsumedMs,
- mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
+ }
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Quota is enforced for top-started job after the process leaves TOP/BTOP state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ trackJobs(job, jobHigh);
+ mQuotaController.prepareForExecutionLocked(job);
+ mQuotaController.prepareForExecutionLocked(jobHigh);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
}
}
@Test
+ public void testGetMaxJobExecutionTimeLocked_Regular_ImportantWhileForeground() {
+ mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
+ createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+ final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS;
+ JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
+ //noinspection deprecation
+ JobStatus jobDefIWF;
+ mSetFlagsRule.disableFlags(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND);
+ jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked_IWF",
+ createJobInfoBuilder(1)
+ .setImportantWhileForeground(true)
+ .setPriority(JobInfo.PRIORITY_DEFAULT)
+ .build());
+
+ setStandbyBucket(RARE_INDEX, jobDefIWF);
+ setCharging();
+ synchronized (mQuotaController.mLock) {
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ }
+
+ setDischarging();
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ }
+
+ // Top-started job
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-stared jobs are out of quota enforcement.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ trackJobs(jobDefIWF);
+ mQuotaController.prepareForExecutionLocked(jobDefIWF);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ }
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Quota is enforced for top-started job after the process leaves TOP/BTOP state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ trackJobs(jobDefIWF);
+ mQuotaController.prepareForExecutionLocked(jobDefIWF);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ }
+
+ mSetFlagsRule.enableFlags(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND);
+ jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked_IWF",
+ createJobInfoBuilder(1)
+ .setImportantWhileForeground(true)
+ .setPriority(JobInfo.PRIORITY_DEFAULT)
+ .build());
+
+ setStandbyBucket(RARE_INDEX, jobDefIWF);
+ setCharging();
+ synchronized (mQuotaController.mLock) {
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ }
+
+ setDischarging();
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ }
+
+ // Top-started job
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-stared jobs are out of quota enforcement.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ trackJobs(jobDefIWF);
+ mQuotaController.prepareForExecutionLocked(jobDefIWF);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ }
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Quota is enforced for top-started job after the process leaves TOP/BTOP state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ trackJobs(jobDefIWF);
+ mQuotaController.prepareForExecutionLocked(jobDefIWF);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ }
+ }
+
+ @Test
public void testGetMaxJobExecutionTimeLocked_Regular_Active() {
JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0);
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
@@ -1455,7 +1951,8 @@ public class QuotaControllerTest {
}
@Test
- public void testGetMaxJobExecutionTimeLocked_EJ() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetMaxJobExecutionTimeLocked_EJ_LegacyDefaultEJLimits() {
final long timeUsedMs = 3 * MINUTE_IN_MILLIS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
@@ -1473,12 +1970,13 @@ public class QuotaControllerTest {
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
@@ -1486,6 +1984,7 @@ public class QuotaControllerTest {
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1497,6 +1996,28 @@ public class QuotaControllerTest {
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket
+ // minus the time has been used.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
// Test used quota rolling out of window.
synchronized (mQuotaController.mLock) {
mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
@@ -1505,12 +2026,13 @@ public class QuotaControllerTest {
createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
timeUsedMs, 5), true);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
@@ -1519,6 +2041,7 @@ public class QuotaControllerTest {
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
mQuotaController.maybeStopTrackingJobLocked(job, null);
@@ -1529,13 +2052,162 @@ public class QuotaControllerTest {
assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
}
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetMaxJobExecutionTimeLocked_EJ_NewDefaultEJLimits() {
+ final long timeUsedMs = 3 * MINUTE_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
+ timeUsedMs, 5), true);
+ JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0);
+ setStandbyBucket(RARE_INDEX, job);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ }
+
+ setCharging();
+ synchronized (mQuotaController.mLock) {
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ setDischarging();
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
+ assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ // Test used quota rolling out of window.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
+ }
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
+ timeUsedMs, 5), true);
+
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ // max of 50% WORKING limit and remaining quota
+ assertEquals(10 * MINUTE_IN_MILLIS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is out of quota enforcement.
+ assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ // Top-started job is enforced by quota policy after the app leaves the TOP state.
+ // The max execution time should be the total EJ session limit of the RARE bucket.
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
}
/**
* Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
@@ -1559,27 +2231,56 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
+ false);
+
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 20 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
+ // window size = allowed time, so jobs can essentially run non-stop until they reach the
+ // max execution time.
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(10 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Close to RARE boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS),
30 * SECOND_IN_MILLIS, 5), false);
// Far away from FREQUENT boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
// Overlap WORKING_SET boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS),
3 * MINUTE_IN_MILLIS, 5), false);
// Close to ACTIVE boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
setStandbyBucket(RARE_INDEX);
synchronized (mQuotaController.mLock) {
@@ -1624,6 +2325,69 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to RARE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS),
+ 30 * SECOND_IN_MILLIS, 5), false);
+ // Far away from FREQUENT boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+ // Overlap WORKING_SET boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+ // Close to ACTIVE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+
+ setStandbyBucket(RARE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(30 * SECOND_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ setStandbyBucket(FREQUENT_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ setStandbyBucket(WORKING_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(5 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ // ACTIVE window != allowed time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(10 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
*/
@@ -1651,7 +2415,7 @@ public class QuotaControllerTest {
// Close to boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS),
- 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5), false);
+ mQcConstants.MAX_EXECUTION_TIME_MS - 5 * MINUTE_IN_MILLIS, 5), false);
setStandbyBucket(WORKING_INDEX);
synchronized (mQuotaController.mLock) {
@@ -1667,7 +2431,8 @@ public class QuotaControllerTest {
// Far from boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5),
+ now - (20 * HOUR_IN_MILLIS),
+ mQcConstants.MAX_EXECUTION_TIME_MS - 3 * MINUTE_IN_MILLIS, 5),
false);
setStandbyBucket(WORKING_INDEX);
@@ -1694,11 +2459,12 @@ public class QuotaControllerTest {
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS),
- 4 * HOUR_IN_MILLIS,
+ mQcConstants.MAX_EXECUTION_TIME_MS,
5), false);
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5),
+ now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS + MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5),
false);
synchronized (mQuotaController.mLock) {
@@ -1722,11 +2488,12 @@ public class QuotaControllerTest {
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
now - (20 * HOUR_IN_MILLIS),
- 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS,
+ mQcConstants.MAX_EXECUTION_TIME_MS - 12 * MINUTE_IN_MILLIS,
5), false);
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5),
+ now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS + MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5),
false);
synchronized (mQuotaController.mLock) {
@@ -1745,7 +2512,8 @@ public class QuotaControllerTest {
* Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (24 * HOUR_IN_MILLIS),
@@ -1771,55 +2539,84 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS),
+ mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, 5),
+ false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (20 * MINUTE_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5),
+ false);
+
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 20 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
+ // window size != allowed time.
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window and the session is rolling out of the window.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS),
- 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_RARE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, 5), false);
setStandbyBucket(RARE_INDEX);
synchronized (mQuotaController.mLock) {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (8 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 5), false);
setStandbyBucket(FREQUENT_INDEX);
synchronized (mQuotaController.mLock) {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS),
- 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, 5), false);
setStandbyBucket(WORKING_INDEX);
synchronized (mQuotaController.mLock) {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
- false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_ACTIVE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 5), false);
// ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
// max execution time.
setStandbyBucket(ACTIVE_INDEX);
@@ -1827,130 +2624,85 @@ public class QuotaControllerTest {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS
+ - (mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS
+ + mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS
+ + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS),
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
}
- /**
- * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
- * window and there are valid QuotaBumps in the history.
- */
@Test
- public void testGetTimeUntilQuotaConsumedLocked_QuotaBump() {
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
-
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow_NewDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- // Close to RARE boundary.
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
- 30 * SECOND_IN_MILLIS, 5), false);
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS));
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS));
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS));
- // Far away from FREQUENT boundary.
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
- // Overlap WORKING_SET boundary.
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 2 * HOUR_IN_MILLIS));
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
- 3 * MINUTE_IN_MILLIS, 5), false);
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS));
- // Close to ACTIVE boundary.
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_RARE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, 5), false);
setStandbyBucket(RARE_INDEX);
synchronized (mQuotaController.mLock) {
- assertEquals(3 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
+ assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(4 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 5), false);
setStandbyBucket(FREQUENT_INDEX);
synchronized (mQuotaController.mLock) {
- assertEquals(4 * MINUTE_IN_MILLIS,
+ assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(4 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, 5), false);
setStandbyBucket(WORKING_INDEX);
synchronized (mQuotaController.mLock) {
- assertEquals(8 * MINUTE_IN_MILLIS,
+ assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
- // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
- // max execution time.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_ACTIVE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 5),
+ false);
+ // ACTIVE window != allowed time.
setStandbyBucket(ACTIVE_INDEX);
synchronized (mQuotaController.mLock) {
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
- }
-
- /**
- * Test getTimeUntilQuotaConsumedLocked when there are valid QuotaBumps in recent history that
- * provide enough additional quota to bridge gaps between sessions.
- */
- @Test
- public void testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps() {
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (25 * HOUR_IN_MILLIS),
- 30 * MINUTE_IN_MILLIS, 25), false);
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS));
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS));
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS));
- // Without the valid quota bumps, the app would only 3 minutes until the quota was consumed.
- // The quota bumps provide enough quota to bridge the gap between the two earliest sessions.
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (8 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (8 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS),
- 2 * MINUTE_IN_MILLIS, 5), false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
- 3 * MINUTE_IN_MILLIS, 1), false);
- mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
- .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS));
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (9 * MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 1), false);
-
- setStandbyBucket(FREQUENT_INDEX);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_EXEMPTED_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 5),
+ false);
+ // EXEMPTED window != allowed time
+ setStandbyBucket(EXEMPTED_INDEX);
synchronized (mQuotaController.mLock) {
- assertEquals(2 * MINUTE_IN_MILLIS,
+ assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(7 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
@@ -2302,270 +3054,6 @@ public class QuotaControllerTest {
}
}
- @Test
- public void testIsWithinQuotaLocked_WithQuotaBump_Duration() {
- setDischarging();
- int standbyBucket = WORKING_INDEX;
- setStandbyBucket(standbyBucket);
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
- 5 * MINUTE_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
-
- long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(
- now - (HOUR_IN_MILLIS - 2 * MINUTE_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1),
- false);
- final ExecutionStats stats;
- synchronized (mQuotaController.mLock) {
- stats = mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
- mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1);
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(5 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- }
- mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- }
-
- advanceElapsedClock(HOUR_IN_MILLIS);
-
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- }
-
- // Emulate a quota bump while some jobs are executing
- JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 1);
- JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 2);
-
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(job1, null);
- mQuotaController.prepareForExecutionLocked(job1);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- mQuotaController.maybeStartTrackingJobLocked(job2, null);
- mQuotaController.prepareForExecutionLocked(job2);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStopTrackingJobLocked(job1, null);
- mQuotaController.maybeStopTrackingJobLocked(job2, null);
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- }
-
- // Phase out the first session
- advanceElapsedClock(5 * MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- }
-
- // Phase out the first quota bump
- advanceElapsedClock(7 * HOUR_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
- }
- }
-
- @Test
- public void testIsWithinQuotaLocked_WithQuotaBump_JobCount() {
- setDischarging();
- int standbyBucket = WORKING_INDEX;
- setStandbyBucket(standbyBucket);
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
- 20 * MINUTE_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 1);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
-
- long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 10), false);
- final ExecutionStats stats;
- synchronized (mQuotaController.mLock) {
- stats = mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
- mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 10);
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(10, stats.jobCountLimit);
- }
- mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(11, stats.jobCountLimit);
- }
-
- advanceElapsedClock(HOUR_IN_MILLIS);
-
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(11, stats.jobCountLimit);
- }
-
- // Emulate a quota bump while some jobs are executing
- JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1);
- JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2);
-
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(job1, null);
- mQuotaController.prepareForExecutionLocked(job1);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(12, stats.jobCountLimit);
- mQuotaController.maybeStartTrackingJobLocked(job2, null);
- mQuotaController.prepareForExecutionLocked(job2);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStopTrackingJobLocked(job1, null);
- mQuotaController.maybeStopTrackingJobLocked(job2, null);
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(12, stats.jobCountLimit);
- }
-
- // Phase out the first session
- advanceElapsedClock(3 * MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(12, stats.jobCountLimit);
- }
-
- // Phase out the first quota bump
- advanceElapsedClock(7 * HOUR_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(11, stats.jobCountLimit);
- }
- }
-
- @Test
- public void testIsWithinQuotaLocked_WithQuotaBump_SessionCount() {
- setDischarging();
- int standbyBucket = WORKING_INDEX;
- setStandbyBucket(standbyBucket);
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
- 20 * MINUTE_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 2);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 1);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
-
- long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
- final ExecutionStats stats;
- synchronized (mQuotaController.mLock) {
- stats = mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(2, stats.sessionCountLimit);
- }
- mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(3, stats.sessionCountLimit);
- }
-
- advanceElapsedClock(HOUR_IN_MILLIS);
-
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(3, stats.sessionCountLimit);
- }
-
- // Emulate a quota bump while some jobs are executing
- JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1);
- JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2);
-
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(job1, null);
- mQuotaController.prepareForExecutionLocked(job1);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStopTrackingJobLocked(job1, null);
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(4, stats.sessionCountLimit);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(job2, null);
- mQuotaController.prepareForExecutionLocked(job2);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStopTrackingJobLocked(job2, null);
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(4, stats.sessionCountLimit);
- }
-
- // Phase out the first session
- advanceElapsedClock(2 * MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(4, stats.sessionCountLimit);
- }
-
- // Phase out the first quota bump
- advanceElapsedClock(7 * HOUR_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(3, stats.sessionCountLimit);
- }
- }
@Test
public void testIsWithinEJQuotaLocked_NeverApp() {
@@ -2928,12 +3416,12 @@ public class QuotaControllerTest {
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
- final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
+ final long end = now - (mQcConstants.WINDOW_SIZE_WORKING_MS - 5 * MINUTE_IN_MILLIS);
// Counting backwards, the quota will come back one minute before the end.
- final long expectedAlarmTime =
- end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
+ final long expectedAlarmTime = end - MINUTE_IN_MILLIS + mQcConstants.WINDOW_SIZE_WORKING_MS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false);
+ new TimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS, end, 1), false);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeScheduleStartAlarmLocked(
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
@@ -3000,7 +3488,8 @@ public class QuotaControllerTest {
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS
+ - 2 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeScheduleStartAlarmLocked(
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
@@ -3009,8 +3498,9 @@ public class QuotaControllerTest {
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
- final long start = now - (6 * HOUR_IN_MILLIS);
- final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
+ final long start = now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - 2 * HOUR_IN_MILLIS);
+ final long expectedAlarmTime = start + mQcConstants.WINDOW_SIZE_FREQUENT_MS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
@@ -3084,7 +3574,8 @@ public class QuotaControllerTest {
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS
+ - 2 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeScheduleStartAlarmLocked(
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
@@ -3093,8 +3584,9 @@ public class QuotaControllerTest {
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
- final long start = now - (6 * HOUR_IN_MILLIS);
- final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
+ final long start = now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - 2 * HOUR_IN_MILLIS);
+ final long expectedAlarmTime = start + mQcConstants.WINDOW_SIZE_FREQUENT_MS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
@@ -3266,7 +3758,7 @@ public class QuotaControllerTest {
// And down from there.
final long expectedWorkingAlarmTime =
- outOfQuotaTime + (2 * HOUR_IN_MILLIS)
+ outOfQuotaTime + mQcConstants.WINDOW_SIZE_WORKING_MS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
setStandbyBucket(WORKING_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
@@ -3278,7 +3770,7 @@ public class QuotaControllerTest {
eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long expectedFrequentAlarmTime =
- outOfQuotaTime + (8 * HOUR_IN_MILLIS)
+ outOfQuotaTime + mQcConstants.WINDOW_SIZE_FREQUENT_MS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
setStandbyBucket(FREQUENT_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
@@ -3290,7 +3782,7 @@ public class QuotaControllerTest {
eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long expectedRareAlarmTime =
- outOfQuotaTime + (24 * HOUR_IN_MILLIS)
+ outOfQuotaTime + mQcConstants.WINDOW_SIZE_RARE_MS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
setStandbyBucket(RARE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
@@ -3450,7 +3942,7 @@ public class QuotaControllerTest {
}
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- // Working set window size is 2 hours.
+ // Working set window size is configured with QcConstants.WINDOW_SIZE_WORKING_MS.
final int standbyBucket = WORKING_INDEX;
final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
final long remainingTimeMs =
@@ -3459,13 +3951,14 @@ public class QuotaControllerTest {
// Session straddles edge of bucket window. Only the contribution should be counted towards
// the quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
- 3 * MINUTE_IN_MILLIS + contributionMs, 3), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS
+ - 3 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS + contributionMs,
+ 3), false);
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2), false);
// Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
// is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
- final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
+ final long expectedAlarmTime = now - HOUR_IN_MILLIS + mQcConstants.WINDOW_SIZE_WORKING_MS
+ (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeScheduleStartAlarmLocked(
@@ -3569,12 +4062,6 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS,
84 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
- 93 * SECOND_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 92);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 91);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 90 * MINUTE_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 89);
assertEquals(8 * MINUTE_IN_MILLIS,
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
@@ -3632,11 +4119,6 @@ public class QuotaControllerTest {
assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
- assertEquals(93 * SECOND_IN_MILLIS, mQuotaController.getQuotaBumpAdditionDurationMs());
- assertEquals(92, mQuotaController.getQuotaBumpAdditionJobCount());
- assertEquals(91, mQuotaController.getQuotaBumpAdditionSessionCount());
- assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
- assertEquals(89, mQuotaController.getQuotaBumpLimit());
}
@Test
@@ -3689,11 +4171,6 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, -1);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, -1);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, -1);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 59 * MINUTE_IN_MILLIS);
- setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, -1);
assertEquals(MINUTE_IN_MILLIS,
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
@@ -3744,11 +4221,6 @@ public class QuotaControllerTest {
assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs());
assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs());
assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs());
- assertEquals(0, mQuotaController.getQuotaBumpAdditionDurationMs());
- assertEquals(0, mQuotaController.getQuotaBumpAdditionJobCount());
- assertEquals(0, mQuotaController.getQuotaBumpAdditionSessionCount());
- assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
- assertEquals(0, mQuotaController.getQuotaBumpLimit());
// Invalid configurations.
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
@@ -3806,7 +4278,6 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS);
assertEquals(24 * HOUR_IN_MILLIS,
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
@@ -3847,7 +4318,6 @@ public class QuotaControllerTest {
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
- assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
}
/** Tests that TimingSessions aren't saved when the device is charging. */
@@ -4126,7 +4596,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
// Change to a state that should still be considered foreground.
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
advanceElapsedClock(5 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
@@ -4134,6 +4604,36 @@ public class QuotaControllerTest {
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ /** Tests that Timers count FOREGROUND_SERVICE jobs. */
+ @Test
+ @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
+ public void testTimerTracking_Fgs() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1);
+ setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ // Change to FOREGROUND_SERVICE state that should count.
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
+ }
+ List<TimingSession> expected = new ArrayList<>();
+ expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when switching between foreground and background
* states.
@@ -4180,7 +4680,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4213,7 +4713,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4262,7 +4762,7 @@ public class QuotaControllerTest {
}
assertEquals(0, stats.jobCountInRateLimitingWindow);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4355,6 +4855,7 @@ public class QuotaControllerTest {
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Bg job starts while inactive, spans an entire active session, and ends after the
// active session.
@@ -4412,7 +4913,7 @@ public class QuotaControllerTest {
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4433,8 +4934,66 @@ public class QuotaControllerTest {
mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
}
+ // jobBg2 and jobFg1 are counted, jobTop is not counted.
expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+
+ // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+ // foreground_service and a new job starts. Shortly after, uid goes
+ // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+ // This should result in two TimingSessions:
+ // * The first should have a count of 1
+ // * The second should have a count of 2, which accounts for the bg2 and fg and top jobs.
+ // Top started jobs are not quota free any more if the process leaves TOP/BTOP state.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
+ }
+ // jobBg2, jobFg1 and jobTop are counted.
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 3));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
@@ -4554,7 +5113,103 @@ public class QuotaControllerTest {
* Tests that TOP jobs aren't stopped when an app runs out of quota.
*/
@Test
- public void testTracking_OutOfQuota_ForegroundAndBackground() {
+ @DisableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testTracking_OutOfQuota_ForegroundAndBackground_DisableTopStartedJobsThrottling() {
+ setDischarging();
+
+ JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+ trackJobs(jobBg, jobTop);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ }
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+ synchronized (mQuotaController.mLock) {
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+ }
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Only Bg job will be changed from in-quota to out-of-quota.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
+ // Top job should still be allowed to run.
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // New jobs to run.
+ JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+ JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
+ trackJobs(jobFg, jobTop);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+ // App is now in background and out of quota. Fg should now change to out of quota since it
+ // wasn't started. Top should remain in quota since it started when the app was in TOP.
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ }
+
+ @Test
+ @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS})
+ @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS,
+ Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS})
+ public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() {
setDischarging();
JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
@@ -4598,6 +5253,7 @@ public class QuotaControllerTest {
// Go to a background state.
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Only Bg job will be changed from in-quota to out-of-quota.
inOrder.verify(mJobSchedulerService,
timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
.onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
@@ -4644,6 +5300,105 @@ public class QuotaControllerTest {
}
/**
+ * Tests that TOP jobs are stopped when an app runs out of quota.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testTracking_OutOfQuota_ForegroundAndBackground_EnableTopStartedJobsThrottling() {
+ setDischarging();
+
+ JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+ trackJobs(jobBg, jobTop);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ }
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+ synchronized (mQuotaController.mLock) {
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+ }
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Both Bg and Top jobs should be changed from in-quota to out-of-quota
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+ // Top job should NOT be allowed to run.
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // New jobs to run.
+ JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+ JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ // Both Bg and Top jobs should be changed from out-of-quota to in-quota.
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+ trackJobs(jobFg, jobTop);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ // App is in background so everything should be out of quota.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Bg, Fg and Top jobs should be changed from in-quota to out-of-quota.
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+ // App is now in background and out of quota. Fg should now change to out of quota
+ // since it wasn't started. Top should now changed to out of quota even it started
+ // when the app was in TOP.
+ assertFalse(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ }
+
+ /**
* Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
* its quota.
*/
@@ -4697,7 +5452,7 @@ public class QuotaControllerTest {
// The package only has one second to run, but this session is at the edge of the rolling
// window, so as the package "reaches its quota" it will have more to keep running.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - 2 * HOUR_IN_MILLIS,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
10 * SECOND_IN_MILLIS - remainingTimeMs, 1), false);
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS,
@@ -5901,7 +6656,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -5935,7 +6690,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -6027,6 +6782,7 @@ public class QuotaControllerTest {
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
// Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
// foreground_service and a new job starts. Shortly after, uid goes
@@ -6056,7 +6812,7 @@ public class QuotaControllerTest {
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -6080,6 +6836,63 @@ public class QuotaControllerTest {
expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS);
+
+ // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+ // foreground_service and a new job starts. Shortly after, uid goes
+ // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+ // This should result in two TimingSessions:
+ // * The first should have a count of 1
+ // * The second should have a count of 3, which accounts for the bg2, fg and top jobs.
+ // Top started jobs are not quota free any more if the process leaves TOP/BTOP state.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
+ }
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 3));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
@@ -6448,7 +7261,8 @@ public class QuotaControllerTest {
* Tests that expedited jobs aren't stopped when an app runs out of quota.
*/
@Test
- public void testEJTracking_OutOfQuota_ForegroundAndBackground() {
+ @DisableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testEJTracking_OutOfQuota_ForegroundAndBackground_DisableTopStartedJobsThrottling() {
setDischarging();
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
@@ -6534,7 +7348,7 @@ public class QuotaControllerTest {
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop2.isExpeditedQuotaApproved());
assertTrue(jobFg.isExpeditedQuotaApproved());
assertTrue(jobBg.isExpeditedQuotaApproved());
@@ -6560,6 +7374,129 @@ public class QuotaControllerTest {
}
/**
+ * Tests that expedited jobs are stopped when an app runs out of quota.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS)
+ public void testEJTracking_OutOfQuota_ForegroundAndBackground_EnableTopStartedJobsThrottling() {
+ setDischarging();
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
+
+ JobStatus jobBg =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 2);
+ JobStatus jobUnstarted =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 3);
+ trackJobs(jobBg, jobTop, jobUnstarted);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg, jobUnstarted);
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ mQcConstants.EJ_LIMIT_WORKING_MS - remainingTimeMs, 1), true);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ }
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+ synchronized (mQuotaController.mLock) {
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Bg, Top and jobUnstarted should be changed from in-quota to out-of-quota.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+ // Top should still NOT be "in quota" even it started before the app
+ // ran on top out of quota.
+ assertFalse(jobBg.isExpeditedQuotaApproved());
+ assertFalse(jobTop.isExpeditedQuotaApproved());
+ assertFalse(jobUnstarted.isExpeditedQuotaApproved());
+ synchronized (mQuotaController.mLock) {
+ assertTrue(
+ 0 >= mQuotaController
+ .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ // New jobs to run.
+ JobStatus jobBg2 =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobTop2 =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 5);
+ JobStatus jobFg =
+ createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 6);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
+ // jobBg, jobFg and jobUnstarted are changed from out-of-quota to in-quota.
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
+ trackJobs(jobTop2, jobFg);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop2);
+ }
+ assertTrue(jobTop.isExpeditedQuotaApproved());
+ assertTrue(jobTop2.isExpeditedQuotaApproved());
+ assertTrue(jobFg.isExpeditedQuotaApproved());
+ assertTrue(jobBg.isExpeditedQuotaApproved());
+ assertTrue(jobUnstarted.isExpeditedQuotaApproved());
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ assertTrue(jobTop.isExpeditedQuotaApproved());
+ assertTrue(jobTop2.isExpeditedQuotaApproved());
+ assertTrue(jobFg.isExpeditedQuotaApproved());
+ assertTrue(jobBg.isExpeditedQuotaApproved());
+ assertTrue(jobUnstarted.isExpeditedQuotaApproved());
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Bg, Fg, Top, Top2 and jobUnstarted should be changed from in-quota to out-of-quota
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 5));
+ // App is now in background and out of quota. Fg should now change to out of quota since it
+ // wasn't started. Top should change to out of quota as the app leaves TOP state.
+ assertFalse(jobTop.isExpeditedQuotaApproved());
+ assertFalse(jobTop2.isExpeditedQuotaApproved());
+ assertFalse(jobFg.isExpeditedQuotaApproved());
+ assertFalse(jobBg.isExpeditedQuotaApproved());
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isExpeditedQuotaApproved());
+ assertFalse(jobUnstarted.isExpeditedQuotaApproved());
+ synchronized (mQuotaController.mLock) {
+ assertTrue(
+ 0 >= mQuotaController
+ .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
+ /**
* Tests that Timers properly track overlapping top and background jobs.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index c2c67e615228..e04aeecd8b5d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -44,10 +44,13 @@ import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.DebugUtils;
@@ -74,6 +77,8 @@ public class ThermalStatusRestrictionTest {
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private ThermalStatusRestriction mThermalStatusRestriction;
private PowerManager.OnThermalStatusChangedListener mStatusChangedListener;
@@ -427,6 +432,7 @@ public class ThermalStatusRestrictionTest {
*/
@Test
@RequiresFlagsEnabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+ @DisableFlags(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsEnabled() {
JobStatusContainer jc =
new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService);
@@ -508,6 +514,91 @@ public class ThermalStatusRestrictionTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+ @EnableFlags(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
+ public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsEnabled_ignoreIWF() {
+ JobStatusContainer jc =
+ new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService);
+ int jobBias = JobInfo.BIAS_FOREGROUND_SERVICE;
+ for (int thermalStatus : jc.thermalStatuses) {
+ String msg = debugTag(jobBias, thermalStatus);
+ mStatusChangedListener.onThermalStatusChanged(thermalStatus);
+ if (thermalStatus >= THERMAL_STATUS_SEVERE) {
+ // Full restrictions on all jobs
+ assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ej, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ui, jobBias));
+ assertTrue(msg, isJobRestricted(jc.uiRetried, jobBias));
+ assertTrue(msg, isJobRestricted(jc.uiRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+ } else if (thermalStatus >= THERMAL_STATUS_MODERATE) {
+ // No restrictions on user related jobs
+ assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+ // Some restrictions on expedited jobs
+ assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+ // Some restrictions on high priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+ // Full restructions on important while foreground jobs as
+ // the important while foreground flag is ignored.
+ assertTrue(isJobRestricted(jc.importantWhileForeground, jobBias));
+ assertTrue(isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+ assertTrue(isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+ // Full restriction on default priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+ // Full restriction on low priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+ // Full restriction on min priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+ } else {
+ // thermalStatus < THERMAL_STATUS_MODERATE
+ // No restrictions on any job type
+ assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.importantWhileForeground, jobBias));
+ assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+ }
+ }
+ }
+
/**
* Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is less than
* Foreground Service and all Thermal states.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index a1937cec706c..9b8a7cca7358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -23,6 +23,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -33,20 +35,24 @@ import android.content.res.Resources;
import android.location.ILocationListener;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
+import android.location.flags.Flags;
import android.location.provider.ProviderRequest;
import android.os.IBinder;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.provider.AbstractLocationProvider;
import com.android.server.location.provider.LocationProviderManager;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.google.common.util.concurrent.MoreExecutors;
@@ -54,6 +60,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -75,8 +82,12 @@ public class LocationManagerServiceTest {
private TestInjector mInjector;
private LocationManagerService mLocationManagerService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Spy private FakeAbstractLocationProvider mProviderWithPermission;
@Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
+ @Mock private ProxyPopulationDensityProvider mPopulationDensityProvider;
@Mock private ILocationListener mLocationListener;
@Mock private IBinder mBinder;
@Mock private Context mContext;
@@ -172,6 +183,32 @@ public class LocationManagerServiceTest {
}
@Test
+ public void testSetLocationFudgerCache_withFeatureFlagDisabled_isNotCalled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager, never()).setLocationFudgerCache(any());
+ }
+
+ @Test
+ public void testSetLocationFudgerCache_withFeatureFlagEnabled_isCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager).setLocationFudgerCache(cache);
+ }
+
+ @Test
public void testHasProvider_noPermission() {
assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
new file mode 100644
index 000000000000..6b7eda26b945
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.fudger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+public class LocationFudgerCacheTest {
+
+ private static final String TAG = "LocationFudgerCacheTest";
+
+ private static final long TIMES_SQUARE_S2_ID =
+ S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+ private static final double[] POINT_IN_TIMES_SQUARE = {40.75889599346095, -73.9851300385147};
+
+ private static final double[] POINT_OUTSIDE_TIMES_SQUARE = {48.858093, 2.294694};
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
+ public void hasDefaultValue_isInitiallyFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_uponQueryError_isStillFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_afterSuccessfulQuery_isTrue()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ assertThat(cache.hasDefaultValue()).isTrue();
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedOutsideOfCache_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ int defaultLevel = 2;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(defaultLevel);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_OUTSIDE_TIMES_SQUARE[0],
+ POINT_OUTSIDE_TIMES_SQUARE[1])).isEqualTo(defaultLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedValueIsCached_returnsCachedValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]))
+ .isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenStarting_queriesDefaultValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifDidntGetDefaultValue_queriesItAgain() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ verify(provider, times(2)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifReceivedDefaultValue_doesNotQueriesIt()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ // Verify getDefaultCoarseningLevel did not get called again
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenSuccessfullyQueriesDefaultValue_storesResult()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(level);
+
+ // Query any uncached location
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryingDefaultValueFails_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ // Query any uncached location. The default value is 0
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(0);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsNotCached_queriesProvider() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+ verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), anyInt(), any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenProviderIsQueried_resultIsCached() throws RemoteException {
+ double lat = POINT_IN_TIMES_SQUARE[0];
+ double lng = POINT_IN_TIMES_SQUARE[1];
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ int level = cache.getCoarseningLevel(lat, lng);
+ assertThat(level).isEqualTo(0); // default value
+
+ ArgumentCaptor<IS2CellIdsCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2CellIdsCallback.class);
+ verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), anyInt(), argumentCaptor.capture());
+
+ // Results from the proxy should set the cache
+ int expectedLevel = 4;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(lat, lng);
+ Long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+ IS2CellIdsCallback cb = argumentCaptor.getValue();
+ long[] answer = new long[] {s2CellId};
+ cb.onResult(answer);
+
+ int level2 = cache.getCoarseningLevel(lat, lng);
+ assertThat(level2).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsCached_doesNotRefreshIt() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.addToCache(TIMES_SQUARE_S2_ID);
+
+ verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsCached_askForMaxCacheSizeElems() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int numAdditionalCells = cache.MAX_CACHE_SIZE - 1;
+
+ cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+ verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), eq(numAdditionalCells), any());
+ }
+
+
+ @Test
+ public void locationFudgerCache_canContainUpToMaxSizeItems() {
+ // This test has two sequences of arrange-act-assert.
+ // The first checks that the cache correctly store up to MAX_CACHE_SIZE items.
+ // The second checks that any new element replaces the oldest in the cache.
+
+ // Arrange.
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int size = cache.MAX_CACHE_SIZE;
+
+ double[][] latlngs = new double[size][2];
+ long[] cells = new long[size];
+ int[] expectedLevels = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ // Create arbitrary lat/lngs.
+ latlngs[i][0] = 10.0 * i;
+ latlngs[i][1] = 10.0 * i;
+
+ expectedLevels[i] = 10; // we set some arbitrary S2 level for each latlng.
+
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latlngs[i][0], latlngs[i][1]);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevels[i]);
+ cells[i] = s2CellId;
+ }
+
+ // Act.
+ cache.addToCache(cells);
+
+ // Assert: check that the cache contains these latlngs and returns the correct level.
+ for (int i = 0; i < size; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+
+ // Second assertion: A new value evicts the oldest one.
+
+ // Arrange.
+ int expectedLevel = 25;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(-10.0, -180.0);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+
+ // Act.
+ cache.addToCache(s2CellId);
+
+ // Assert: the new point is in the cache.
+ assertThat(cache.getCoarseningLevel(-10.0, -180.0)).isEqualTo(expectedLevel);
+ // Assert: all but the oldest point are still in cache.
+ for (int i = 0; i < size - 1; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+ // Assert: the oldest point has been evicted.
+ assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1]))
+ .isEqualTo(0);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index 4e9b6c78edfd..835705d49e6e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -22,14 +22,23 @@ import static com.android.server.location.LocationUtils.createLocation;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.location.Location;
+import android.location.flags.Flags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +64,9 @@ public class LocationFudgerTest {
private LocationFudger mFudger;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
long seed = System.currentTimeMillis();
@@ -162,4 +174,64 @@ public class LocationFudgerTest {
input.getLongitude() + deltaYM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
0);
}
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsDisabled_cacheIsNotUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledButNoDefaultValue_cacheIsNotUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(false).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledAndDefaultIsSet_cacheIsUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(true).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ Location fine = createLocation("test", mRandom);
+ mFudger.createCoarse(fine);
+
+ // We can't verify that the coordinatese of "fine" are passed to the API due to the addition
+ // of the offset. We must use anyDouble().
+ verify(cache).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_newAlgorithm_snapsToCenterOfS2Cell_testVector() {
+ // NB: a complete test vector is in
+ // frameworks/base/services/tests/mockingservicestests/src/com/android/server/...
+ // location/geometry/S2CellIdUtilsTest.java
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ // Arbitrary location in Times Square, NYC
+ double[] latLng = new double[] {40.758896, -73.985130};
+ int s2Level = 1;
+ // The level-2 S2 cell around this location is "8c", its center is:
+ double[] expected = { 21.037511025421814, -67.38013505195958 };
+
+ double[] center = mFudger.snapToCenterOfS2Cell(latLng[0], latLng[1], s2Level);
+
+ assertThat(center[0]).isEqualTo(expected[0]);
+ assertThat(center[1]).isEqualTo(expected[1]);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java
new file mode 100644
index 000000000000..9e43b8122cd3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.geometry;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class S2CellIdUtilsTest {
+
+ // S2 cell ID of a level-30 cell in Times Square.
+ private static final long TIMES_SQUARE_S2_ID =
+ S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+ // Position of the Eiffel tower (outside of any parent cell from Times Square).
+ private static final double[] EIFFEL_TOWER_LATLNG = {48.858093, 2.294694};
+
+ // Test vector around TIMES_SQUARE_S2_ID: Cell IDs and the centers for levels 0 to 30.
+ // This test vector has been computed using the public S2 library in
+ // external/s2-geometry-library-java
+ private static final CellAndCenter[] TIMES_SQUARE_CELLS = {
+ new CellAndCenter("9", -0.0, -90.0),
+ new CellAndCenter("8c", 21.037511025421814, -67.38013505195958),
+ new CellAndCenter("89", 34.04786296943431, -79.38034472384487),
+ new CellAndCenter("89c", 38.79459515585768, -73.46516214265485),
+ new CellAndCenter("89d", 41.74704688465104, -76.45630866778862),
+ new CellAndCenter("89c4", 40.29416073145462, -74.96763653470293),
+ new CellAndCenter("89c3", 40.827706513259564, -74.21793256064282),
+ new CellAndCenter("89c24", 40.45771021423038, -73.84190634077625),
+ new CellAndCenter("89c25", 40.64307662867646, -74.03001224983848),
+ new CellAndCenter("89c25c", 40.708880489804564, -73.93598211433742),
+ new CellAndCenter("89c259", 40.75509755935301, -73.9830029344863),
+ new CellAndCenter("89c2584", 40.7781887758716, -74.00650903621303),
+ new CellAndCenter("89c2585", 40.766644611813284, -73.99475634561863),
+ new CellAndCenter("89c25854", 40.76087144655763, -73.98887973002674),
+ new CellAndCenter("89c25855", 40.75798459318946, -73.98594135473846),
+ new CellAndCenter("89c25855c", 40.75901097797799, -73.98447215023141),
+ new CellAndCenter("89c25855b", 40.758497791893824, -73.98520675388987),
+ new CellAndCenter("89c25855bc", 40.75875438651343, -73.98483945241185),
+ new CellAndCenter("89c25855b9", 40.758934819692875, -73.98502310323867),
+ new CellAndCenter("89c25855b9c", 40.75887067137071, -73.98511492858621),
+ new CellAndCenter("89c25855b9d", 40.75891577956465, -73.98516084124353),
+ new CellAndCenter("89c25855b9c4", 40.758893225473194, -73.98513788491626),
+ new CellAndCenter("89c25855b9c7", 40.75890124402366, -73.98512640675159),
+ new CellAndCenter("89c25855b9c6c", 40.758897234748815, -73.985132145834),
+ new CellAndCenter("89c25855b9c6d", 40.75889441548664, -73.98512927629281),
+ new CellAndCenter("89c25855b9c6c4", 40.75889582511775, -73.98513071106342),
+ new CellAndCenter("89c25855b9c6c3", 40.758896326277146, -73.98512999367811),
+ new CellAndCenter("89c25855b9c6c3c", 40.75889607569745, -73.98513035237076),
+ new CellAndCenter("89c25855b9c6c39", 40.75889589949357, -73.98513017302443),
+ new CellAndCenter("89c25855b9c6c39c", 40.75889596213849, -73.98513008335128),
+ new CellAndCenter("89c25855b9c6c39f", 40.75889599346095, -73.9851300385147)};
+
+ @Test
+ public void toLatLngDegrees_matchesTestVector() {
+ for (int level = 0; level <= 30; level++) {
+ double[] expected = TIMES_SQUARE_CELLS[level].mCenter;
+ long cellId = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ double[] centerPoint = {0.0, 0.0};
+ S2CellIdUtils.toLatLngDegrees(cellId, centerPoint);
+
+ assertThat(approxEquals(centerPoint[0], expected[0])).isTrue();
+ assertThat(approxEquals(centerPoint[1], expected[1])).isTrue();
+ }
+ }
+
+ private static boolean approxEquals(double a, double b) {
+ return Math.abs(a - b) <= 1e-14;
+ }
+
+ @Test
+ public void containsLatLngDegrees_eachCellContainsItsCenter_works() {
+ for (int level = 0; level <= 30; level++) {
+ long cellId = TIMES_SQUARE_CELLS[level].toCellId();
+ double[] center = TIMES_SQUARE_CELLS[level].mCenter;
+
+ boolean isContained = S2CellIdUtils.containsLatLngDegrees(cellId, center[0], center[1]);
+
+ assertThat(isContained).isTrue();
+ }
+ }
+
+ @Test
+ public void containsLatLngDegrees_testWithOutsidePoint() {
+ for (int level = 0; level <= 30; level++) {
+ long cellId = TIMES_SQUARE_CELLS[level].toCellId();
+
+ assertThat(S2CellIdUtils.containsLatLngDegrees(cellId, EIFFEL_TOWER_LATLNG[0],
+ EIFFEL_TOWER_LATLNG[1])).isFalse();
+ }
+ }
+
+ // A tuple with a S2 cell id, and a S2LatLng representing its center.
+ private static class CellAndCenter {
+ public String mToken;
+ public double[] mCenter;
+
+ CellAndCenter(String token, double latDegrees, double lngDegrees) {
+ this.mToken = token;
+ this.mCenter = new double[] {latDegrees, lngDegrees};
+ }
+
+ // Converts from hex representation to long format.
+ long toCellId() {
+ long value = 0;
+ for (int pos = 0; pos < mToken.length(); pos++) {
+ int digitValue = Character.digit(mToken.charAt(pos), 16);
+ if (digitValue == -1) {
+ return -1;
+ }
+ value = value * 16 + digitValue;
+ }
+ value = value << (4 * (16 - mToken.length())); // remove implicit zeros
+ return value;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 09282646ff68..6d78defe2943 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -40,6 +40,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -72,6 +73,7 @@ import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
+import android.location.provider.IS2LevelCallback;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -98,8 +100,10 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import org.junit.After;
import org.junit.Before;
@@ -1432,6 +1436,72 @@ public class LocationProviderManagerTest {
PERMISSION_FINE)).isEqualTo(location);
}
+ @Test
+ public void testLocationFudger_withFlagDisabled_cacheIsNotSetAndOldAlgoIsUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabledButNoDefaults_oldAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+
+ // Act: the provider didn't provide a default
+ cb.onError();
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabled_cacheIsSetAndNewAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int defaultLevel = 2;
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+ cb.onResult(defaultLevel);
+
+ Location test = new Location("any-provider");
+ test.setLatitude(10.0);
+ test.setLongitude(20.0);
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ // We can't test that 10.0, 20.0 was passed due to the offset. We only test that a call
+ // happened.
+ verify(provider).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
index 574f3699edb8..ae404cff7171 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY;
import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY;
+import static com.android.server.pm.BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.after;
@@ -84,12 +85,18 @@ public class BackgroundInstallControlCallbackHelperTest {
int testUserId = 1;
mCallbackHelper.registerBackgroundInstallCallback(mCallback);
- mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName);
+ mCallbackHelper.notifyAllCallbacks(
+ testUserId,
+ testPackageName,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture());
Bundle receivedBundle = bundleCaptor.getValue();
assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY));
assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY));
+ assertEquals(
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL,
+ receivedBundle.getInt(INSTALL_EVENT_TYPE_KEY));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index 9ba272446689..3d0c63780ef3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.media.AudioAttributes.USAGE_ALARM;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -40,14 +41,19 @@ import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.PlayerProxy;
import android.media.audiopolicy.AudioPolicy;
+import android.multiuser.Flags;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -59,7 +65,6 @@ import java.util.List;
import java.util.Stack;
@RunWith(JUnit4.class)
-
public class BackgroundUserSoundNotifierTest {
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -71,6 +76,10 @@ public class BackgroundUserSoundNotifierTest {
@Mock
private NotificationManager mNotificationManager;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -91,8 +100,10 @@ public class BackgroundUserSoundNotifierTest {
}
@Test
public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException {
+ assumeTrue(UserManager.supportsMultipleUsers());
AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
final int fgUserId = mSpiedContext.getUserId();
final int bgUserUid = user.id * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -100,7 +111,7 @@ public class BackgroundUserSoundNotifierTest {
/* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
- mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
verify(mNotificationManager)
.notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
eq(afi.getClientUid()), any(Notification.class),
@@ -116,7 +127,7 @@ public class BackgroundUserSoundNotifierTest {
/* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
- mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
verifyZeroInteractions(mNotificationManager);
}
@@ -131,7 +142,7 @@ public class BackgroundUserSoundNotifierTest {
AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0,
Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
- mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
verifyZeroInteractions(mNotificationManager);
}
@@ -140,8 +151,11 @@ public class BackgroundUserSoundNotifierTest {
final int fgUserId = mSpiedContext.getUserId();
int bgUserId = fgUserId + 1;
int bgUserUid = bgUserId * 100000;
- mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
-
+ if (Flags.multipleAlarmNotificationsSupport()) {
+ mBackgroundUserSoundNotifier.mNotificationClientUids.add(bgUserUid);
+ } else {
+ mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
+ }
AudioManager mockAudioManager = mock(AudioManager.class);
when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);
@@ -169,7 +183,7 @@ public class BackgroundUserSoundNotifierTest {
doReturn(focusStack).when(mockAudioPolicy).getFocusStack();
mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy;
- mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext);
+ mBackgroundUserSoundNotifier.muteAlarmSounds(bgUserUid);
verify(apc1.getPlayerProxy()).stop();
verify(mockAudioPolicy).sendFocusLossAndUpdate(afi);
@@ -178,6 +192,7 @@ public class BackgroundUserSoundNotifierTest {
@Test
public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() {
+ assumeTrue(UserManager.supportsMultipleUsers());
final int fgUserId = mSpiedContext.getUserId();
UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
int bgUserUid = bgUser.id * 100000;
@@ -195,6 +210,28 @@ public class BackgroundUserSoundNotifierTest {
eq(UserHandle.of(fgUserId)));
}
+ @Test
+ public void testOnAudioFocusGrant_alarmOnProfileOfForegroundUser_foregroundUserNotNotified() {
+ assumeTrue(UserManager.supportsMultipleUsers());
+ final int fgUserId = mSpiedContext.getUserId();
+ UserInfo fgUserProfile = createProfileForUser("Background profile",
+ UserManager.USER_TYPE_PROFILE_MANAGED, fgUserId, null);
+ assumeTrue("Cannot add a profile", fgUserProfile != null);
+ int fgUserProfileUid = fgUserProfile.id * 100_000;
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, fgUserProfileUid, "", "",
+ AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.getAudioPolicyFocusListener()
+ .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mNotificationManager, never())
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
@Test
public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() {
@@ -205,7 +242,7 @@ public class BackgroundUserSoundNotifierTest {
.when(mUserManager).getUserSwitchability(any());
Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
- mSpiedContext);
+ mSpiedContext, 101000);
assertEquals("Alarm for BgUser", notification.extras.getString(
Notification.EXTRA_TITLE));
@@ -232,7 +269,7 @@ public class BackgroundUserSoundNotifierTest {
.when(mUserManager).getUserSwitchability(any());
Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
- mSpiedContext);
+ mSpiedContext, 101000);
assertEquals(1, notification.actions.length);
assertEquals(mSpiedContext.getString(
@@ -240,6 +277,72 @@ public class BackgroundUserSoundNotifierTest {
notification.actions[0].title);
}
+ @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
+ @Test
+ public void testMultipleAlarmsSameUid_OneNotificationCreated() throws RemoteException {
+ assumeTrue(UserManager.supportsMultipleUsers());
+ UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ final int fgUserId = mSpiedContext.getUserId();
+ final int bgUserUid = user.id * 100000;
+ doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi1.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+
+ AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+ clearInvocations(mNotificationManager);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
+ verify(mNotificationManager, never())
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi2.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+ @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
+ @Test
+ public void testMultipleAlarmsDifferentUsers_multipleNotificationsCreated()
+ throws RemoteException {
+ assumeTrue(UserManager.supportsMultipleUsers());
+ UserInfo user1 = createUser("User1", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ UserInfo user2 = createUser("User2", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ final int fgUserId = mSpiedContext.getUserId();
+ final int bgUserUid1 = user1.id * 100000;
+ final int bgUserUid2 = user2.id * 100000;
+ doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid1, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi1.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+
+ AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid2, "",
+ /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
+ clearInvocations(mNotificationManager);
+ mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi2.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+
private UserInfo createUser(String name, String userType, int flags) {
UserInfo user = mUserManager.createUser(name, userType, flags);
if (user != null) {
@@ -247,6 +350,17 @@ public class BackgroundUserSoundNotifierTest {
}
return user;
}
+
+ private UserInfo createProfileForUser(String name, String userType, int userHandle,
+ String[] disallowedPackages) {
+ UserInfo profile = mUserManager.createProfileForUser(
+ name, userType, 0, userHandle, disallowedPackages);
+ if (profile != null) {
+ mUsersToRemove.add(profile.id);
+ }
+ return profile;
+ }
+
private void removeUser(int userId) {
mUserManager.removeUser(userId);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index e131a98b52d0..90e1263b76ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -56,8 +56,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
testHandler.flush()
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java),
- pkgListCaptor.capture(), any(), any(), flagsCaptor.capture())
+ verify(broadcastHelper).sendDistractingPackagesChanged(
+ any(), pkgListCaptor.capture(), any(), any(), flagsCaptor.capture())
val modifiedPackages = pkgListCaptor.value
val distractionFlags = flagsCaptor.value
@@ -158,8 +158,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendDistractingPackagesChanged(
- any(Computer::class.java), pkgListCaptor.capture(), any(), eq(TEST_USER_ID),
- flagsCaptor.capture())
+ any(), pkgListCaptor.capture(), any(), eq(TEST_USER_ID), flagsCaptor.capture())
val modifiedPackages = pkgListCaptor.value
val distractionFlags = flagsCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -185,7 +184,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable(),
+ nullable())
distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
arrayOfNulls(0), TEST_USER_ID)
@@ -197,12 +197,12 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
@Test
fun sendDistractingPackagesChanged() {
- broadcastHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
+ broadcastHelper.sendDistractingPackagesChanged(pms::snapshotComputer,
packagesToChange, uidsToChange, TEST_USER_ID,
PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
testHandler.flush()
- verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java),
- pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any())
+ verify(broadcastHelper).sendDistractingPackagesChanged(
+ any(), pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any())
var changedPackages = pkgListCaptor.value
var changedUids = uidsCaptor.value
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
new file mode 100644
index 000000000000..0304a74f7654
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@RunWith(JUnit4.class)
+@RequiresFlagsEnabled(FLAG_SDK_DEPENDENCY_INSTALLER)
+public class InstallDependencyHelperTest {
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @Rule public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
+ private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @Mock private SharedLibrariesImpl mSharedLibraries;
+ @Mock private Context mContext;
+ @Mock private Computer mComputer;
+ @Mock private PackageInstallerService mPackageInstallerService;
+ private InstallDependencyHelper mInstallDependencyHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries,
+ mPackageInstallerService);
+ }
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
+ throws Exception {
+ doThrow(new PackageManagerException(new Exception("xyz")))
+ .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
+
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
+ callback.assertFailure();
+
+ assertThat(callback.error).hasMessageThat().contains("xyz");
+ }
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
+ // Return a non-empty list as missing dependency
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ List<SharedLibraryInfo> missingDependency = Collections.singletonList(
+ mock(SharedLibraryInfo.class));
+ when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+ .thenReturn(missingDependency);
+
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
+ callback.assertFailure();
+
+ assertThat(callback.error).hasMessageThat().contains(
+ "Dependency Installer Service not found");
+ }
+
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_allDependenciesInstalled() throws Exception {
+ // Return an empty list as missing dependency
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ List<SharedLibraryInfo> missingDependency = Collections.emptyList();
+ when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+ .thenReturn(missingDependency);
+
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
+ callback.assertSuccess();
+ }
+
+ private static class CallbackHelper implements OutcomeReceiver<Void, PackageManagerException> {
+ public PackageManagerException error;
+
+ private final CountDownLatch mWait = new CountDownLatch(1);
+ private final boolean mExpectSuccess;
+
+ CallbackHelper(boolean expectSuccess) {
+ mExpectSuccess = expectSuccess;
+ }
+
+ @Override
+ public void onResult(Void result) {
+ if (!mExpectSuccess) {
+ fail("Expected to fail");
+ }
+ mWait.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException e) {
+ if (mExpectSuccess) {
+ fail("Expected success but received: " + e);
+ }
+ error = e;
+ mWait.countDown();
+ }
+
+ void assertSuccess() throws Exception {
+ assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(error).isNull();
+ }
+
+ void assertFailure() throws Exception {
+ assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(error).isNotNull();
+ }
+
+ }
+
+ private PackageLite getPackageLite(String apkFileName) throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+ ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+ ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+ ApkLite baseApk = result.getResult();
+
+ return new PackageLite(/*path=*/ null, baseApk.getPath(), baseApk,
+ /*splitNames=*/ null, /*isFeatureSplits=*/ null, /*usesSplitNames=*/ null,
+ /*configForSplit=*/ null, /*splitApkPaths=*/ null,
+ /*splitRevisionCodes=*/ null, baseApk.getTargetSdkVersion(),
+ /*requiredSplitTypes=*/ null, /*splitTypes=*/ null);
+ }
+
+ private File copyApkToTmpDir(String apkFileName) throws Exception {
+ File outFile = temporaryFolder.newFile(apkFileName);
+ String apkFilePath = PUSH_FILE_DIR + apkFileName;
+ File apkFile = new File(apkFilePath);
+ assertThat(apkFile.exists()).isTrue();
+ try (InputStream is = new FileInputStream(apkFile)) {
+ FileUtils.copyToFileOrThrow(is, outFile);
+ }
+ return outFile;
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 0a6edf1b9831..b53dbc834351 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -218,6 +218,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
val handler = TestHandler(null)
val defaultAppProvider: DefaultAppProvider = mock()
val backgroundHandler = TestHandler(null)
+ val packageInstallerService: PackageInstallerService = mock()
+ val installDependencyHelper: InstallDependencyHelper = mock()
val updateOwnershipHelper: UpdateOwnershipHelper = mock()
}
@@ -306,6 +308,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
whenever(mocks.injector.handler) { mocks.handler }
whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler }
+ whenever(mocks.injector.packageInstallerService) { mocks.packageInstallerService }
whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper }
whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager }
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
@@ -332,6 +335,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
DEVICE_PROVISIONING_PACKAGE_NAME
}
whenever(mocks.apexManager.activeApexInfos).thenReturn(DEFAULT_ACTIVE_APEX_INFO_LIST)
+ whenever(mocks.packageInstallerService.installDependencyHelper).thenReturn(
+ mocks.installDependencyHelper)
whenever(mocks.settings.packagesLocked).thenReturn(mSettingsMap)
whenever(mocks.settings.internalVersion).thenReturn(DEFAULT_VERSION_INFO)
whenever(mocks.settings.keySetManagerService).thenReturn(mocks.keySetManagerService)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 24e7242792fb..54ee2a3ae0d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -33,6 +33,7 @@ import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Process;
+import android.os.UserHandle;
import android.util.SparseArray;
import org.junit.After;
@@ -340,6 +341,24 @@ public class PackageMonitorCallbackHelperTest {
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
+ @Test
+ public void registerPackageMonitor_callbackNotInAllowListSystemUidSecondUser_callbackIsCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ int userId = 10;
+ int fakeAppId = 12345;
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(userId, new int[]{UserHandle.getUid(userId, fakeAppId)});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId,
+ UserHandle.getUid(userId, Process.SYSTEM_UID));
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{userId},
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
private IRemoteCallback createMockPackageMonitorCallback() {
return spy(new IRemoteCallback.Stub() {
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 52fa331cf7c5..6f5e2b713260 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -740,7 +740,8 @@ public class StagingManagerTest {
/* isApplied */false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
/* stagedSessionErrorMessage */ "no error",
- /* preVerifiedDomains */ null);
+ /* preVerifiedDomains */ null,
+ /* installDependencyHelper */ null);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 7444403f88c8..60e82501db1c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -58,8 +58,8 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
testHandler.flush()
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
- eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any())
+ verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(),
+ eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any())
var modifiedPackages = pkgListCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -149,11 +149,11 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
testHandler.flush()
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
- eq(Intent.ACTION_PACKAGES_UNSUSPENDED), pkgListCaptor.capture(), any(), any(),
- any())
- verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java),
- any(), any(), any())
+ verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(
+ any(), eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
+ pkgListCaptor.capture(), any(), any(), any())
+ verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(
+ any(), any(), any(), any())
var modifiedPackages = pkgListCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -230,10 +230,10 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
testHandler.flush()
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
- eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any())
- verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java),
- any(), any(), any())
+ verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(
+ any(), eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any())
+ verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(
+ any(), any(), any(), any())
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1cba3c574543..8a10040f986f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -195,12 +195,12 @@ public final class UserManagerServiceTest {
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
mockIsLowRamDevice(false);
- // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+ // Called when getting boot user. config_bootToHeadlessSystemUser is 0 by default.
mSpyResources = spy(mSpiedContext.getResources());
when(mSpiedContext.getResources()).thenReturn(mSpyResources);
- doReturn(false)
+ doReturn(0)
.when(mSpyResources)
- .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -859,15 +859,50 @@ public final class UserManagerServiceTest {
}
@Test
- public void testGetBootUser_enableBootToHeadlessSystemUser() {
+ public void testGetBootUser_Headless_BootToSystemUserWhenDeviceIsProvisioned() {
setSystemUserHeadless(true);
- doReturn(true)
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+ mockProvisionedDevice(true);
+ doReturn(1)
.when(mSpyResources)
- .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
}
+ @Test
+ public void testGetBootUser_Headless_BootToFirstSwitchableFullUserWhenDeviceNotProvisioned() {
+ setSystemUserHeadless(true);
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+ mockProvisionedDevice(false);
+ doReturn(1)
+ .when(mSpyResources)
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+ // Even if the headless system user switchable flag is true, the boot user should be the
+ // first switchable full user.
+ doReturn(true)
+ .when(mSpyResources)
+ .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
+
+ assertThat(mUms.getBootUser()).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetBootUser_Headless_ThrowsIfBootFailsNoFullUserWhenDeviceNotProvisioned()
+ throws Exception {
+ setSystemUserHeadless(true);
+ removeNonSystemUsers();
+ mockProvisionedDevice(false);
+ doReturn(1)
+ .when(mSpyResources)
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.getBootUser());
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -935,6 +970,11 @@ public final class UserManagerServiceTest {
any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
}
+ private void mockProvisionedDevice(boolean isProvisionedDevice) {
+ doReturn(isProvisionedDevice ? 1 : 0).when(() -> Settings.Global.getInt(
+ any(), eq(android.provider.Settings.Global.DEVICE_PROVISIONED), anyInt()));
+ }
+
private void mockIsLowRamDevice(boolean isLowRamDevice) {
doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
index aa9d8c6ea713..f1072da4161f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
@@ -18,6 +18,7 @@ package com.android.server.power;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -29,10 +30,16 @@ import android.hardware.thermal.TemperatureType;
import android.hardware.thermal.ThrottlingSeverity;
import android.os.Binder;
import android.os.CoolingDevice;
+import android.os.Flags;
import android.os.RemoteException;
import android.os.Temperature;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
@@ -40,16 +47,36 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class ThermalManagerServiceMockingTest {
- @Mock private IThermal mAidlHalMock;
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+
+ @Mock
+ private IThermal mAidlHalMock;
private Binder mAidlBinder = new Binder();
private CompletableFuture<Temperature> mTemperatureFuture;
- private ThermalManagerService.ThermalHalWrapper.TemperatureChangedCallback mTemperatureCallback;
+ private CompletableFuture<TemperatureThreshold> mThresholdFuture;
+ private ThermalManagerService.ThermalHalWrapper.WrapperThermalChangedCallback
+ mTemperatureCallback =
+ new ThermalManagerService.ThermalHalWrapper.WrapperThermalChangedCallback() {
+ @Override
+ public void onTemperatureChanged(Temperature temperature) {
+ mTemperatureFuture.complete(temperature);
+ }
+
+ @Override
+ public void onThresholdChanged(TemperatureThreshold threshold) {
+ mThresholdFuture.complete(threshold);
+ }
+ };
private ThermalManagerService.ThermalHalAidlWrapper mAidlWrapper;
@Captor
ArgumentCaptor<IThermalChangedCallback> mAidlCallbackCaptor;
@@ -60,27 +87,63 @@ public class ThermalManagerServiceMockingTest {
Mockito.when(mAidlHalMock.asBinder()).thenReturn(mAidlBinder);
mAidlBinder.attachInterface(mAidlHalMock, IThermal.class.getName());
mTemperatureFuture = new CompletableFuture<>();
- mTemperatureCallback = temperature -> mTemperatureFuture.complete(temperature);
+ mThresholdFuture = new CompletableFuture<>();
mAidlWrapper = new ThermalManagerService.ThermalHalAidlWrapper(mTemperatureCallback);
mAidlWrapper.initProxyAndRegisterCallback(mAidlBinder);
}
@Test
+ @EnableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK})
public void setCallback_aidl() throws Exception {
Mockito.verify(mAidlHalMock, Mockito.times(1)).registerThermalChangedCallback(
mAidlCallbackCaptor.capture());
- android.hardware.thermal.Temperature halT =
+ android.hardware.thermal.Temperature halTemperature =
new android.hardware.thermal.Temperature();
- halT.type = TemperatureType.SOC;
- halT.name = "test";
- halT.throttlingStatus = ThrottlingSeverity.SHUTDOWN;
- halT.value = 99.0f;
- mAidlCallbackCaptor.getValue().notifyThrottling(halT);
+ halTemperature.type = TemperatureType.SOC;
+ halTemperature.name = "test";
+ halTemperature.throttlingStatus = ThrottlingSeverity.SHUTDOWN;
+ halTemperature.value = 99.0f;
+
+ android.hardware.thermal.TemperatureThreshold halThreshold =
+ new android.hardware.thermal.TemperatureThreshold();
+ halThreshold.type = TemperatureType.SKIN;
+ halThreshold.name = "test";
+ halThreshold.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
+ Arrays.fill(halThreshold.hotThrottlingThresholds, Float.NaN);
+ halThreshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE] = 44.0f;
+
+ mAidlCallbackCaptor.getValue().notifyThrottling(halTemperature);
+ mAidlCallbackCaptor.getValue().notifyThresholdChanged(halThreshold);
+
Temperature temperature = mTemperatureFuture.get(100, TimeUnit.MILLISECONDS);
- assertEquals(halT.name, temperature.getName());
- assertEquals(halT.type, temperature.getType());
- assertEquals(halT.value, temperature.getValue(), 0.1f);
- assertEquals(halT.throttlingStatus, temperature.getStatus());
+ assertEquals(halTemperature.name, temperature.getName());
+ assertEquals(halTemperature.type, temperature.getType());
+ assertEquals(halTemperature.value, temperature.getValue(), 0.1f);
+ assertEquals(halTemperature.throttlingStatus, temperature.getStatus());
+
+ TemperatureThreshold threshold = mThresholdFuture.get(100, TimeUnit.MILLISECONDS);
+ assertEquals(halThreshold.name, threshold.name);
+ assertEquals(halThreshold.type, threshold.type);
+ assertArrayEquals(halThreshold.hotThrottlingThresholds, threshold.hotThrottlingThresholds,
+ 0.01f);
+ }
+
+ @Test
+ @DisableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK})
+ public void setCallback_aidl_allow_thermal_thresholds_callback_false() throws Exception {
+ Mockito.verify(mAidlHalMock, Mockito.times(1)).registerThermalChangedCallback(
+ mAidlCallbackCaptor.capture());
+ android.hardware.thermal.TemperatureThreshold halThreshold =
+ new android.hardware.thermal.TemperatureThreshold();
+ halThreshold.type = TemperatureType.SOC;
+ halThreshold.name = "test";
+ halThreshold.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
+ Arrays.fill(halThreshold.hotThrottlingThresholds, Float.NaN);
+ halThreshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE] = 44.0f;
+
+ mAidlCallbackCaptor.getValue().notifyThresholdChanged(halThreshold);
+ Thread.sleep(1000);
+ assertFalse(mThresholdFuture.isDone());
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index 677ecf47355d..5a802d9de2ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -34,7 +34,10 @@ android_test {
"services.core",
"truth",
"flag-junit",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": ["service-crashrecovery-pre-jarjar"],
+ default: [],
+ }),
libs: [
"android.test.mock.stubs.system",
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index 6e416857abbf..347dc81c6734 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -36,6 +37,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -47,6 +49,7 @@ import android.os.MessageQueue;
import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -106,14 +109,11 @@ public class RollbackPackageHealthObserverTest {
private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
"persist.device_config.configuration.disable_high_impact_rollback";
- private SystemConfig mSysConfig;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@Before
public void setup() {
- mSysConfig = new SystemConfigTestClass();
-
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
@@ -145,6 +145,22 @@ public class RollbackPackageHealthObserverTest {
}
).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
+ try {
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
+ final PackageInfo res = new PackageInfo();
+ res.packageName = inv.getArgument(0);
+ res.setApexPackageName(res.packageName);
+ return res;
+ });
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ Context testContext = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext();
+ when(mMockContext.getUser()).thenReturn(testContext.getUser());
+ when(mMockContext.getPackageName()).thenReturn(testContext.getPackageName());
+
SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(false));
}
@@ -165,7 +181,7 @@ public class RollbackPackageHealthObserverTest {
@Test
public void testHealthCheckLevels() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
@@ -209,14 +225,14 @@ public class RollbackPackageHealthObserverTest {
@Test
public void testIsPersistent() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
assertTrue(observer.isPersistent());
}
@Test
public void testMayObservePackage_withoutAnyRollback() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
assertFalse(observer.mayObservePackage(APP_A));
@@ -226,7 +242,7 @@ public class RollbackPackageHealthObserverTest {
public void testMayObservePackage_forPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM;
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -241,7 +257,7 @@ public class RollbackPackageHealthObserverTest {
public void testMayObservePackage_forNonPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
@@ -267,7 +283,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_LOW);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -298,7 +314,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -329,7 +345,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -367,7 +383,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 222,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -400,7 +416,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_LOW);
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
// Make the rollbacks available
@@ -408,7 +424,7 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(secondFailedPackage,
+ observer.onExecuteHealthCheckMitigation(secondFailedPackage,
PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
@@ -442,7 +458,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 222,
PackageManager.ROLLBACK_USER_IMPACT_LOW);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -452,7 +468,8 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ observer.onExecuteHealthCheckMitigation(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH,
+ 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager).commitRollback(argument.capture(), any(), any());
@@ -487,7 +504,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_LOW);
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -497,7 +514,8 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ observer.onExecuteHealthCheckMitigation(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(2)).commitRollback(
@@ -533,7 +551,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -543,7 +561,8 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ observer.onExecuteHealthCheckMitigation(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(1)).commitRollback(
@@ -571,7 +590,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -580,7 +599,8 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ observer.onExecuteHealthCheckMitigation(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
@@ -602,7 +622,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_LOW);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
// Make the rollbacks available
@@ -627,7 +647,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
// Make the rollbacks available
@@ -653,7 +673,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
// Make the rollbacks available
@@ -682,7 +702,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
// Make the rollbacks available
@@ -718,7 +738,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 222,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
// Make the rollbacks available
@@ -757,7 +777,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 222,
PackageManager.ROLLBACK_USER_IMPACT_LOW);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -767,7 +787,7 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.executeBootLoopMitigation(1);
+ observer.onExecuteBootLoopMitigation(1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(2)).commitRollback(
@@ -802,7 +822,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 222,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -812,7 +832,7 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.executeBootLoopMitigation(1);
+ observer.onExecuteBootLoopMitigation(1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(1)).commitRollback(
@@ -838,7 +858,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -847,7 +867,7 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.executeBootLoopMitigation(1);
+ observer.onExecuteBootLoopMitigation(1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(1)).commitRollback(
@@ -883,7 +903,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -893,7 +913,8 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ observer.onExecuteHealthCheckMitigation(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(1)).commitRollback(
@@ -919,7 +940,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -928,7 +949,8 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ observer.onExecuteHealthCheckMitigation(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
@@ -961,7 +983,7 @@ public class RollbackPackageHealthObserverTest {
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -971,7 +993,7 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.executeBootLoopMitigation(1);
+ observer.onExecuteBootLoopMitigation(1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, times(1)).commitRollback(
@@ -1007,7 +1029,7 @@ public class RollbackPackageHealthObserverTest {
false, null, 111,
PackageManager.ROLLBACK_USER_IMPACT_HIGH);
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mMockContext));
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -1017,7 +1039,7 @@ public class RollbackPackageHealthObserverTest {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- observer.executeBootLoopMitigation(1);
+ observer.onExecuteBootLoopMitigation(1);
waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
verify(mRollbackManager, never()).commitRollback(
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index f1bf86f2f57c..b53f6fbee183 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -16,11 +16,11 @@
package com.android.server.wallpaper;
-import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
-import static android.app.WallpaperManager.PORTRAIT;
-import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
-import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
@@ -406,15 +406,16 @@ public class WallpaperCropperTest {
setUpWithDisplays(STANDARD_DISPLAY);
Point bitmapSize = new Point(800, 1000);
SparseArray<Rect> suggestedCrops = new SparseArray<>();
- suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
- for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+ suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800));
+ for (int otherOrientation: List.of(ORIENTATION_LANDSCAPE, ORIENTATION_SQUARE_LANDSCAPE,
+ ORIENTATION_SQUARE_PORTRAIT)) {
suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
}
for (boolean rtl : List.of(false, true)) {
assertThat(mWallpaperCropper.getCrop(
new Point(300, 800), bitmapSize, suggestedCrops, rtl))
- .isEqualTo(suggestedCrops.get(PORTRAIT));
+ .isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT));
assertThat(mWallpaperCropper.getCrop(
new Point(500, 800), bitmapSize, suggestedCrops, rtl))
.isEqualTo(new Rect(0, 0, 500, 800));
@@ -440,8 +441,8 @@ public class WallpaperCropperTest {
Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
Point squarePortrait = SQUARE_PORTRAIT_ONE;
Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
- suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
- suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+ suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait));
+ suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
for (boolean rtl : List.of(false, true)) {
assertThat(mWallpaperCropper.getCrop(
landscape, bitmapSize, suggestedCrops, rtl))
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 9983fb4748a5..db323f1b68e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -40,13 +40,13 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -55,6 +55,7 @@ import android.app.AppOpsManager;
import android.app.Flags;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.app.wallpaper.WallpaperDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -65,6 +66,7 @@ import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.hardware.display.DisplayManager;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
@@ -426,7 +428,8 @@ public class WallpaperManagerServiceTests {
@Test
@EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT)
- public void testSaveLoadSettings() {
+ public void testSaveLoadSettings_withoutWallpaperDescription()
+ throws IOException, XmlPullParserException {
WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
expectedData.setComponent(sDefaultWallpaperComponent);
expectedData.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -436,27 +439,19 @@ public class WallpaperManagerServiceTests {
expectedData.mUidToDimAmount.put(1, 0.4f);
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
- try {
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
- mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, expectedData, null);
- ostream.close();
- } catch (IOException e) {
- fail("exception occurred while writing system wallpaper attributes");
- }
+ TypedXmlSerializer serializer = Xml.newBinarySerializer();
+ serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
+ mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, expectedData, null);
+ ostream.close();
WallpaperData actualData = new WallpaperData(0, FLAG_SYSTEM);
- try {
- ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
- TypedXmlPullParser parser = Xml.newBinaryPullParser();
- parser.setInput(istream, StandardCharsets.UTF_8.name());
- mService.mWallpaperDataParser.loadSettingsFromSerializer(parser,
- actualData, /* userId= */0, /* loadSystem= */ true, /* loadLock= */
- false, /* keepDimensionHints= */ true,
- new WallpaperDisplayHelper.DisplayData(0));
- } catch (IOException | XmlPullParserException e) {
- fail("exception occurred while parsing wallpaper");
- }
+ ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ parser.setInput(istream, StandardCharsets.UTF_8.name());
+ mService.mWallpaperDataParser.loadSettingsFromSerializer(parser,
+ actualData, /* userId= */0, /* loadSystem= */ true, /* loadLock= */
+ false, /* keepDimensionHints= */ true,
+ new WallpaperDisplayHelper.DisplayData(0));
assertThat(actualData.getComponent()).isEqualTo(expectedData.getComponent());
assertThat(actualData.primaryColors).isEqualTo(expectedData.primaryColors);
@@ -472,33 +467,58 @@ public class WallpaperManagerServiceTests {
}
@Test
+ @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT)
+ public void testSaveLoadSettings_withWallpaperDescription()
+ throws IOException, XmlPullParserException {
+ WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
+ expectedData.setComponent(sDefaultWallpaperComponent);
+ PersistableBundle content = new PersistableBundle();
+ content.putString("ckey", "cvalue");
+ WallpaperDescription description = new WallpaperDescription.Builder()
+ .setComponent(sDefaultWallpaperComponent).setId("testId").setTitle("fake one")
+ .setContent(content).build();
+ expectedData.setDescription(description);
+
+ ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+ TypedXmlSerializer serializer = Xml.newBinarySerializer();
+ serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
+ mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, expectedData, null);
+ ostream.close();
+
+ WallpaperData actualData = new WallpaperData(0, FLAG_SYSTEM);
+ ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ parser.setInput(istream, StandardCharsets.UTF_8.name());
+ mService.mWallpaperDataParser.loadSettingsFromSerializer(parser,
+ actualData, /* userId= */0, /* loadSystem= */ true, /* loadLock= */
+ false, /* keepDimensionHints= */ true,
+ new WallpaperDisplayHelper.DisplayData(0));
+
+ assertThat(actualData.getComponent()).isEqualTo(expectedData.getComponent());
+ assertThat(actualData.getDescription()).isEqualTo(expectedData.getDescription());
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT)
- public void testSaveLoadSettings_legacyNextComponent() {
+ public void testSaveLoadSettings_legacyNextComponent()
+ throws IOException, XmlPullParserException {
WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
systemWallpaperData.setComponent(sDefaultWallpaperComponent);
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
- try {
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
- mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, systemWallpaperData,
- null);
- ostream.close();
- } catch (IOException e) {
- fail("exception occurred while writing system wallpaper attributes");
- }
+ TypedXmlSerializer serializer = Xml.newBinarySerializer();
+ serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
+ mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, systemWallpaperData,
+ null);
+ ostream.close();
WallpaperData shouldMatchSystem = new WallpaperData(0, FLAG_SYSTEM);
- try {
- ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
- TypedXmlPullParser parser = Xml.newBinaryPullParser();
- parser.setInput(istream, StandardCharsets.UTF_8.name());
- mService.mWallpaperDataParser.loadSettingsFromSerializer(parser,
- shouldMatchSystem, /* userId= */0, /* loadSystem= */ true, /* loadLock= */
- false, /* keepDimensionHints= */ true,
- new WallpaperDisplayHelper.DisplayData(0));
- } catch (IOException | XmlPullParserException e) {
- fail("exception occurred while parsing wallpaper");
- }
+ ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ parser.setInput(istream, StandardCharsets.UTF_8.name());
+ mService.mWallpaperDataParser.loadSettingsFromSerializer(parser,
+ shouldMatchSystem, /* userId= */0, /* loadSystem= */ true, /* loadLock= */
+ false, /* keepDimensionHints= */ true,
+ new WallpaperDisplayHelper.DisplayData(0));
assertThat(shouldMatchSystem.nextWallpaperComponent).isEqualTo(
systemWallpaperData.getComponent());
@@ -506,6 +526,7 @@ public class WallpaperManagerServiceTests {
}
@Test
+ @Ignore("b/372942682")
public void testWallpaperManagerCallbackInRightOrder() throws RemoteException {
WallpaperData wallpaper = new WallpaperData(USER_SYSTEM, FLAG_SYSTEM);
wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -515,7 +536,8 @@ public class WallpaperManagerServiceTests {
doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
doNothing().when(mService).switchWallpaper(any(), any());
doReturn(true).when(mService)
- .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+ .bindWallpaperComponentLocked(isA(ComponentName.class), anyBoolean(), anyBoolean(),
+ any(), any());
doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
spyOn(mService.mWallpaperCropper);
doNothing().when(mService.mWallpaperCropper).generateCrop(wallpaper);
diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp
index a31a3fb65700..f235da2fea7f 100644
--- a/services/tests/ondeviceintelligencetests/Android.bp
+++ b/services/tests/ondeviceintelligencetests/Android.bp
@@ -44,14 +44,18 @@ android_test {
"truth",
"frameworks-base-testutils",
"androidx.test.rules",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_ondevice_intelligence_module"), {
+ "true": [
+ "service-ondeviceintelligence.impl",
+ ],
+ default: [],
+ }),
libs: [
"android.test.mock.stubs.system",
"android.test.base.stubs.system",
"android.test.runner.stubs.system",
],
-
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
diff --git a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
index d3943e32ef07..28ccb84c38f3 100644
--- a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
+++ b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
@@ -18,10 +18,9 @@ package com.android.server.ondeviceintelligence;
import static com.google.common.truth.Truth.assertThat;
+import android.app.ondeviceintelligence.InferenceInfo;
import android.os.Bundle;
import android.os.PersistableBundle;
-import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
-import android.app.ondeviceintelligence.InferenceInfo;
import android.util.Base64;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -32,13 +31,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class InferenceInfoStoreTest {
InferenceInfoStore inferenceInfoStore;
+ public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info";
+
@Before
public void setUp() {
inferenceInfoStore = new InferenceInfoStore(1000);
@@ -47,27 +47,27 @@ public class InferenceInfoStoreTest {
@Test
public void testInferenceInfoParsesFromBundleSuccessfully() throws Exception {
Bundle bundle = new Bundle();
- bundle.putByteArray(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putByteArray(INFERENCE_INFO_BUNDLE_KEY,
getInferenceInfoBytes(1, 1, 100));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
assertThat(inferenceInfos).hasSize(1);
assertThat(inferenceInfos.get(0).getUid()).isEqualTo(1);
- assertThat(inferenceInfos.get(0).getStartTimeMs()).isEqualTo(1);
- assertThat(inferenceInfos.get(0).getEndTimeMs()).isEqualTo(100);
+ assertThat(inferenceInfos.get(0).getStartTimeMillis()).isEqualTo(1);
+ assertThat(inferenceInfos.get(0).getEndTimeMillis()).isEqualTo(100);
}
@Test
public void testInferenceInfoParsesFromPersistableBundleSuccessfully() throws Exception {
PersistableBundle bundle = new PersistableBundle();
- bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
Base64.encodeToString(getInferenceInfoBytes(1, 1, 100), Base64.DEFAULT));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
assertThat(inferenceInfos).hasSize(1);
assertThat(inferenceInfos.get(0).getUid()).isEqualTo(1);
- assertThat(inferenceInfos.get(0).getStartTimeMs()).isEqualTo(1);
- assertThat(inferenceInfos.get(0).getEndTimeMs()).isEqualTo(100);
+ assertThat(inferenceInfos.get(0).getStartTimeMillis()).isEqualTo(1);
+ assertThat(inferenceInfos.get(0).getEndTimeMillis()).isEqualTo(100);
}
@@ -75,11 +75,11 @@ public class InferenceInfoStoreTest {
public void testEvictionAfterMaxAge() throws Exception {
PersistableBundle bundle = new PersistableBundle();
long testStartTime = System.currentTimeMillis();
- bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 10,
testStartTime + 100), Base64.DEFAULT));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
- bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 5,
testStartTime + 100), Base64.DEFAULT));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
@@ -87,8 +87,8 @@ public class InferenceInfoStoreTest {
List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
assertThat(inferenceInfos).hasSize(2);
assertThat(inferenceInfos.get(0).getUid()).isEqualTo(1);
- assertThat(inferenceInfos.get(0).getStartTimeMs()).isEqualTo(testStartTime - 10);
- assertThat(inferenceInfos.get(0).getEndTimeMs()).isEqualTo(testStartTime + 100);
+ assertThat(inferenceInfos.get(0).getStartTimeMillis()).isEqualTo(testStartTime - 10);
+ assertThat(inferenceInfos.get(0).getEndTimeMillis()).isEqualTo(testStartTime + 100);
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
List<InferenceInfo> inferenceInfos2 = inferenceInfoStore.getLatestInferenceInfo(0);
assertThat(inferenceInfos2).hasSize(1); //previous entries should have been evicted
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 639ae30c00b9..b166514ce0b9 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -51,16 +51,25 @@ import android.content.pm.PackageManager;
import android.hardware.common.fmq.MQDescriptor;
import android.hardware.power.ChannelConfig;
import android.hardware.power.ChannelMessage;
+import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
+import android.hardware.power.SupportInfo;
import android.hardware.power.WorkDuration;
import android.os.Binder;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
import android.os.IBinder;
+import android.os.IHintManager;
import android.os.IHintSession;
import android.os.PerformanceHintManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SessionCreationConfig;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -81,7 +90,6 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -129,11 +137,11 @@ public class HintManagerServiceTest {
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
- makeWorkDuration(1L, 11L, 1L, 8L, 4L),
- makeWorkDuration(2L, 13L, 2L, 8L, 6L),
- makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
- makeWorkDuration(2L, 13L, 2L, 0L, 6L),
- makeWorkDuration(2L, 13L, 2L, 8L, 0L),
+ makeWorkDuration(1L, 11L, 1L, 8L, 4L),
+ makeWorkDuration(2L, 13L, 2L, 8L, 6L),
+ makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
+ makeWorkDuration(2L, 13L, 2L, 0L, 6L),
+ makeWorkDuration(2L, 13L, 2L, 8L, 0L),
};
private static final String TEST_APP_NAME = "com.android.test.app";
@@ -147,12 +155,15 @@ public class HintManagerServiceTest {
private ActivityManagerInternal mAmInternalMock;
@Mock
private PackageManager mMockPackageManager;
+ @Mock
+ private IHintManager.IHintManagerClient mClientCallback;
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
private HintManagerService mService;
private ChannelConfig mConfig;
+ private SupportInfo mSupportInfo;
private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
return new Answer<Long>() {
@@ -163,6 +174,23 @@ public class HintManagerServiceTest {
};
}
+ private SupportInfo makeDefaultSupportInfo() {
+ mSupportInfo = new SupportInfo();
+ mSupportInfo.usesSessions = true;
+ // By default, mark everything as fully supported
+ mSupportInfo.sessionHints = -1;
+ mSupportInfo.sessionModes = -1;
+ mSupportInfo.modes = -1;
+ mSupportInfo.boosts = -1;
+ mSupportInfo.sessionTags = -1;
+ mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+ mSupportInfo.headroom.isCpuSupported = true;
+ mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
+ mSupportInfo.headroom.isGpuSupported = true;
+ mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
+ return mSupportInfo;
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -173,6 +201,7 @@ public class HintManagerServiceTest {
mConfig.eventFlagDescriptor = new MQDescriptor<Byte, Byte>();
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ mSupportInfo = makeDefaultSupportInfo();
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
@@ -188,18 +217,20 @@ public class HintManagerServiceTest {
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
eq(SESSION_TIDS_A), eq(DEFAULT_TARGET_DURATION), anyInt(),
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[0],
- SESSION_IDS[0]));
+ SESSION_IDS[0]));
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
eq(SESSION_TIDS_B), eq(DOUBLED_TARGET_DURATION), anyInt(),
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[1],
- SESSION_IDS[1]));
+ SESSION_IDS[1]));
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
eq(SESSION_TIDS_C), eq(0L), anyInt(),
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2],
SESSION_IDS[2]));
- when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(6);
+ when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo);
when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
+ when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
}
@@ -218,8 +249,8 @@ public class HintManagerServiceTest {
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
eq(0L))).thenReturn(SESSION_PTRS[2]);
when(mNativeWrapperMock.halCreateHintSessionWithConfig(anyInt(), anyInt(),
- any(int[].class), anyLong(), anyInt(),
- any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
+ any(int[].class), anyLong(), anyInt(),
+ any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
}
static class NativeWrapperFake extends NativeWrapper {
@@ -301,6 +332,14 @@ public class HintManagerServiceTest {
return mService;
}
+ private SessionCreationConfig makeSessionCreationConfig(int[] tids,
+ long targetWorkDurationNanos) {
+ SessionCreationConfig config = new SessionCreationConfig();
+ config.tids = tids;
+ config.targetWorkDurationNanos = targetWorkDurationNanos;
+ return config;
+ }
+
@Test
public void testInitializeService() {
HintManagerService service = createService();
@@ -312,12 +351,14 @@ public class HintManagerServiceTest {
public void testCreateHintSessionInvalidPid() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(new int[]{TID, 1}, DEFAULT_TARGET_DURATION);
// Make sure we throw exception when adding a TID doesn't belong to the processes
// In this case, we add `init` PID into the list.
SessionConfig config = new SessionConfig();
assertThrows(SecurityException.class,
() -> service.getBinderServiceInstance().createHintSessionWithConfig(token,
- new int[]{TID, 1}, DEFAULT_TARGET_DURATION, SessionTag.OTHER, config));
+ SessionTag.OTHER, creationConfig, config));
}
@Test
@@ -325,42 +366,54 @@ public class HintManagerServiceTest {
HintManagerService service = createService();
IBinder token = new Binder();
makeConfigCreationUnsupported();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, creationConfig, new SessionConfig());
assertNotNull(a);
+ creationConfig.tids = SESSION_TIDS_B;
+ creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION;
IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_B, DOUBLED_TARGET_DURATION, SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, creationConfig, new SessionConfig());
assertNotEquals(a, b);
+ creationConfig.tids = SESSION_TIDS_C;
+ creationConfig.targetWorkDurationNanos = 0L;
IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_C, 0L, SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, creationConfig, new SessionConfig());
assertNotNull(c);
verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(),
- any(int[].class), anyLong());
+ any(int[].class), anyLong());
}
@Test
public void testCreateHintSessionWithConfig() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
SessionConfig config = new SessionConfig();
IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, config);
+ SessionTag.OTHER, creationConfig, config);
assertNotNull(a);
assertEquals(SESSION_IDS[0], config.id);
SessionConfig config2 = new SessionConfig();
+ creationConfig.tids = SESSION_TIDS_B;
+ creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION;
IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_B, DOUBLED_TARGET_DURATION, SessionTag.APP, config2);
+ SessionTag.APP, creationConfig, config2);
assertNotEquals(a, b);
assertEquals(SESSION_IDS[1], config2.id);
SessionConfig config3 = new SessionConfig();
+ creationConfig.tids = SESSION_TIDS_C;
+ creationConfig.targetWorkDurationNanos = 0L;
IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_C, 0L, SessionTag.GAME, config3);
+ SessionTag.GAME, creationConfig, config3);
assertNotNull(c);
assertEquals(SESSION_IDS[2], config3.id);
verify(mNativeWrapperMock, times(3)).halCreateHintSessionWithConfig(anyInt(), anyInt(),
@@ -368,13 +421,51 @@ public class HintManagerServiceTest {
}
@Test
+ public void testCreateGraphicsPipelineSessions() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+
+ IHintManager.HintManagerClientData data = service.getBinderServiceInstance()
+ .registerClient(mClientCallback);
+
+ final int threadCount = data.maxGraphicsPipelineThreads;
+
+ long sessionPtr1 = 1111L;
+ long sessionId1 = 11111L;
+ CountDownLatch stopLatch1 = new CountDownLatch(1);
+ int[] tids1 = createThreads(threadCount, stopLatch1);
+ when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1),
+ eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class)))
+ .thenAnswer(fakeCreateWithConfig(sessionPtr1, sessionId1));
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION);
+
+ creationConfig.modesToEnable = new int[] {1}; // GRAPHICS_PIPELINE
+
+ SessionConfig config = new SessionConfig();
+ IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
+ SessionTag.OTHER, creationConfig, config);
+ assertNotNull(a);
+ assertEquals(sessionId1, config.id);
+
+ creationConfig.tids = createThreads(1, stopLatch1);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ service.getBinderServiceInstance().createHintSessionWithConfig(token,
+ SessionTag.OTHER, creationConfig, config);
+ });
+ }
+
+ @Test
public void testPauseResumeHintSession() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
// Set session to background and calling updateHintAllowedByProcState() would invoke
// pause();
@@ -410,9 +501,11 @@ public class HintManagerServiceTest {
public void testCloseHintSession() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, creationConfig, new SessionConfig());
a.close();
verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong());
@@ -422,9 +515,11 @@ public class HintManagerServiceTest {
public void testUpdateTargetWorkDuration() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
- SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, creationConfig, new SessionConfig());
assertThrows(IllegalArgumentException.class, () -> {
a.updateTargetWorkDuration(-1L);
@@ -442,10 +537,12 @@ public class HintManagerServiceTest {
public void testReportActualWorkDuration() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
a.updateTargetWorkDuration(100L);
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
@@ -485,10 +582,12 @@ public class HintManagerServiceTest {
public void testSendHint() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
@@ -512,10 +611,12 @@ public class HintManagerServiceTest {
public void testDoHintInBackground() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
@@ -534,10 +635,12 @@ public class HintManagerServiceTest {
public void testDoHintInForeground() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
@@ -548,10 +651,12 @@ public class HintManagerServiceTest {
public void testSetThreads() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
a.updateTargetWorkDuration(100L);
@@ -581,17 +686,17 @@ public class HintManagerServiceTest {
HintManagerService service = createService();
IBinder token = new Binder();
int threadCount = 2;
-
- // session 1 has 2 non-isolated tids
long sessionPtr1 = 111;
CountDownLatch stopLatch1 = new CountDownLatch(1);
int[] tids1 = createThreads(threadCount, stopLatch1);
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1),
eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class)))
.thenReturn(sessionPtr1);
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION);
AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, tids1, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
assertNotNull(session1);
// trigger UID state change by making the process foreground->background, but because the
@@ -618,79 +723,36 @@ public class HintManagerServiceTest {
IBinder token = new Binder();
int threadCount = 2;
- // session 1 has 2 non-isolated tids
long sessionPtr1 = 111;
CountDownLatch stopLatch1 = new CountDownLatch(1);
int[] tids1 = createThreads(threadCount, stopLatch1);
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1),
eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class)))
.thenReturn(sessionPtr1);
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION);
AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, tids1, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
assertNotNull(session1);
- // session 2 has 2 non-isolated tids and 2 isolated tids
- long sessionPtr2 = 222;
- CountDownLatch stopLatch2 = new CountDownLatch(1);
- // negative value used for test only to avoid conflicting with any real thread that exists
- int isoProc1 = -100;
- int isoProc2 = 99999999;
- when(mAmInternalMock.getIsolatedProcesses(eq(UID))).thenReturn(List.of(0));
- int[] tids2 = createThreads(threadCount, stopLatch2);
- int[] tids2WithIsolated = Arrays.copyOf(tids2, tids2.length + 2);
- tids2WithIsolated[threadCount] = isoProc1;
- tids2WithIsolated[threadCount + 1] = isoProc2;
- int[] expectedTids2 = Arrays.copyOf(tids2, tids2.length + 1);
- expectedTids2[tids2.length] = isoProc1;
- when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
- eq(tids2WithIsolated), eq(DEFAULT_TARGET_DURATION), anyInt(),
- any(SessionConfig.class))).thenReturn(sessionPtr2);
- AppHintSession session2 = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, tids2WithIsolated,
- DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig());
- assertNotNull(session2);
-
- // trigger clean up through UID state change by making the process foreground->background
- // this will remove the one unexpected isolated tid from session 2
- service.mUidObserver.onUidStateChanged(UID,
- ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
- service.mUidObserver.onUidStateChanged(UID,
- ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
- LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos(
- CLEAN_UP_UID_DELAY_MILLIS));
- verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
- verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any());
- // the new TIDs pending list should be updated
- assertArrayEquals(expectedTids2, session2.getTidsInternal());
- reset(mNativeWrapperMock);
-
- // this should resume and update the threads so those never-existed invalid isolated
- // processes should be cleaned up
- service.mUidObserver.onUidStateChanged(UID,
- ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
- // wait for the async uid state change to trigger resume and setThreads
- LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
- verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr2), eq(expectedTids2));
- reset(mNativeWrapperMock);
-
// let all session 1 threads to exit and the cleanup should force pause the session 1
stopLatch1.countDown();
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
service.mUidObserver.onUidStateChanged(UID,
+ ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+ service.mUidObserver.onUidStateChanged(UID,
ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos(
CLEAN_UP_UID_DELAY_MILLIS));
verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1));
verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
- verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any());
verifyAllHintsEnabled(session1, false);
- verifyAllHintsEnabled(session2, false);
service.mUidObserver.onUidStateChanged(UID,
ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
+ assertArrayEquals(tids1, session1.getTidsInternal());
verifyAllHintsEnabled(session1, false);
- verifyAllHintsEnabled(session2, true);
reset(mNativeWrapperMock);
// in foreground, set new tids for session 1 then it should be resumed and all hints allowed
@@ -704,8 +766,6 @@ public class HintManagerServiceTest {
// let all session 1 and 2 non isolated threads to exit
stopLatch1.countDown();
- stopLatch2.countDown();
- expectedTids2 = new int[]{isoProc1};
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
service.mUidObserver.onUidStateChanged(UID,
ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
@@ -713,14 +773,11 @@ public class HintManagerServiceTest {
CLEAN_UP_UID_DELAY_MILLIS));
verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1));
verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
- verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any());
// in background, set threads for session 1 then it should not be force paused next time
session1.setThreads(SESSION_TIDS_A);
// the new TIDs pending list should be updated
assertArrayEquals(SESSION_TIDS_A, session1.getTidsInternal());
- assertArrayEquals(expectedTids2, session2.getTidsInternal());
verifyAllHintsEnabled(session1, false);
- verifyAllHintsEnabled(session2, false);
reset(mNativeWrapperMock);
service.mUidObserver.onUidStateChanged(UID,
@@ -729,10 +786,7 @@ public class HintManagerServiceTest {
CLEAN_UP_UID_DELAY_MILLIS));
verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1),
eq(SESSION_TIDS_A));
- verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr2),
- eq(expectedTids2));
verifyAllHintsEnabled(session1, true);
- verifyAllHintsEnabled(session2, true);
}
private void verifyAllHintsEnabled(AppHintSession session, boolean verifyEnabled) {
@@ -785,10 +839,12 @@ public class HintManagerServiceTest {
public void testSetMode() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
a.setMode(0, true);
verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
@@ -797,12 +853,19 @@ public class HintManagerServiceTest {
a.setMode(0, false);
verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
eq(0), eq(false));
+ }
- assertThrows(IllegalArgumentException.class, () -> {
- a.setMode(-1, true);
- });
+ @Test
+ public void testSetModeSessionInBackGround() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
- reset(mNativeWrapperMock);
// Set session to background, then the duration would not be updated.
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
@@ -814,6 +877,40 @@ public class HintManagerServiceTest {
}
@Test
+ public void testSetModeInvalid() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.setMode(-1, true);
+ });
+ }
+
+ @Test
+ public void testSetModeUponSessionCreation() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+ creationConfig.modesToEnable = new int[] {0, 1};
+
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
+ assertNotNull(a);
+ verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
+ eq(0), eq(true));
+ verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
+ eq(1), eq(true));
+ }
+
+ @Test
public void testGetChannel() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
@@ -1001,9 +1098,12 @@ public class HintManagerServiceTest {
private void runAppHintSession(HintManagerService service, int logId,
AtomicReference<Boolean> shouldRun) throws Exception {
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
// we will start some threads and get their valid TIDs to update
int threadCount = 3;
// the list of TIDs
@@ -1068,10 +1168,12 @@ public class HintManagerServiceTest {
public void testReportActualWorkDuration2() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
+ SessionCreationConfig creationConfig =
+ makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
- .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ .createHintSessionWithConfig(token, SessionTag.OTHER,
+ creationConfig, new SessionConfig());
a.updateTargetWorkDuration(100L);
a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
@@ -1152,4 +1254,232 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
assertTrue(service.hasChannel(TGID, UID));
}
+
+ @Test
+ public void testHeadroomPowerHalNotSupported() throws Exception {
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+ HintManagerService service = createService();
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getCpuHeadroom(null);
+ });
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getGpuHeadroom(null);
+ });
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
+ });
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
+ });
+ }
+
+ @Test
+ public void testCpuHeadroomCache() throws Exception {
+ CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
+ CpuHeadroomParams halParams1 = new CpuHeadroomParams();
+ halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams1.tids = new int[]{Process.myPid()};
+
+ CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
+ params2.usesDeviceHeadroom = true;
+ params2.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ CpuHeadroomParams halParams2 = new CpuHeadroomParams();
+ halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams2.tids = new int[]{};
+
+ CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal();
+ params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ CpuHeadroomParams halParams3 = new CpuHeadroomParams();
+ halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ halParams3.tids = new int[]{Process.myPid()};
+
+ // this params should not be cached as the window is not default
+ CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal();
+ params4.calculationWindowMillis = 123;
+ CpuHeadroomParams halParams4 = new CpuHeadroomParams();
+ halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams4.calculationWindowMillis = 123;
+ halParams4.tids = new int[]{Process.myPid()};
+
+ float headroom1 = 0.1f;
+ CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+ float headroom2 = 0.2f;
+ CpuHeadroomResult halRet2 = CpuHeadroomResult.globalHeadroom(headroom2);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2);
+ float headroom3 = 0.3f;
+ CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams3))).thenReturn(halRet3);
+ float headroom4 = 0.4f;
+ CpuHeadroomResult halRet4 = CpuHeadroomResult.globalHeadroom(headroom4);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams4))).thenReturn(halRet4);
+
+ HintManagerService service = createService();
+ clearInvocations(mIPowerMock);
+
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
+
+ // verify cache is working
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
+
+ // after 1 more second it should be served with cache still
+ Thread.sleep(1000);
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
+
+ // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
+ Thread.sleep(1100);
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(4)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
+ }
+
+ @Test
+ public void testGpuHeadroomCache() throws Exception {
+ GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal();
+ GpuHeadroomParams halParams1 = new GpuHeadroomParams();
+ halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
+
+ GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
+ params2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ params2.calculationWindowMillis = 123;
+ GpuHeadroomParams halParams2 = new GpuHeadroomParams();
+ halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationWindowMillis = 123;
+
+ float headroom1 = 0.1f;
+ GpuHeadroomResult halRet1 = GpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+ float headroom2 = 0.2f;
+ GpuHeadroomResult halRet2 = GpuHeadroomResult.globalHeadroom(headroom2);
+ when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(halRet2);
+ HintManagerService service = createService();
+ clearInvocations(mIPowerMock);
+
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(2)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+
+ // verify cache is working
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+
+ // after 1 more second it should be served with cache still
+ Thread.sleep(1000);
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+
+ // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
+ Thread.sleep(1100);
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(2)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+ }
+
+ @Test
+ public void testRegisteringClient() throws Exception {
+ HintManagerService service = createService();
+ IHintManager.HintManagerClientData data = service.getBinderServiceInstance()
+ .registerClient(mClientCallback);
+ assertNotNull(data);
+ assertEquals(data.supportInfo, mSupportInfo);
+ }
+
+ @Test
+ public void testRegisteringClientOnV4() throws Exception {
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(4);
+ HintManagerService service = createService();
+ IHintManager.HintManagerClientData data = service.getBinderServiceInstance()
+ .registerClient(mClientCallback);
+ assertNotNull(data);
+ assertEquals(data.supportInfo.usesSessions, true);
+ assertEquals(data.supportInfo.boosts, 0);
+ assertEquals(data.supportInfo.modes, 0);
+ assertEquals(data.supportInfo.sessionHints, 31);
+ assertEquals(data.supportInfo.sessionModes, 0);
+ assertEquals(data.supportInfo.sessionTags, 0);
+ assertEquals(data.powerHalVersion, 4);
+ assertEquals(data.preferredRateNanos, DEFAULT_HINT_PREFERRED_RATE);
+ }
+
+ @Test
+ public void testRegisteringClientOnV5() throws Exception {
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+ HintManagerService service = createService();
+ IHintManager.HintManagerClientData data = service.getBinderServiceInstance()
+ .registerClient(mClientCallback);
+ assertNotNull(data);
+ assertEquals(data.supportInfo.usesSessions, true);
+ assertEquals(data.supportInfo.boosts, 0);
+ assertEquals(data.supportInfo.modes, 0);
+ assertEquals(data.supportInfo.sessionHints, 255);
+ assertEquals(data.supportInfo.sessionModes, 1);
+ assertEquals(data.supportInfo.sessionTags, 31);
+ assertEquals(data.powerHalVersion, 5);
+ assertEquals(data.preferredRateNanos, DEFAULT_HINT_PREFERRED_RATE);
+ }
+
+ @Test
+ public void testSettingUpOldClientWhenUnsupported() throws Exception {
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+ // Mock unsupported to modify the default support behavior
+ when(mNativeWrapperMock.halGetHintSessionPreferredRate())
+ .thenReturn(-1L);
+ HintManagerService service = createService();
+ IHintManager.HintManagerClientData data = service.getBinderServiceInstance()
+ .registerClient(mClientCallback);
+ assertNotNull(data);
+ assertEquals(data.supportInfo.usesSessions, false);
+ assertEquals(data.supportInfo.boosts, 0);
+ assertEquals(data.supportInfo.modes, 0);
+ assertEquals(data.supportInfo.sessionHints, 0);
+ assertEquals(data.supportInfo.sessionModes, 0);
+ assertEquals(data.supportInfo.sessionTags, 0);
+ assertEquals(data.powerHalVersion, 5);
+ assertEquals(data.preferredRateNanos, -1);
+ }
}
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
index f03043ea0ae0..2f0633139331 100644
--- a/services/tests/powerservicetests/Android.bp
+++ b/services/tests/powerservicetests/Android.bp
@@ -12,6 +12,7 @@ android_test {
],
static_libs: [
+ "truth",
"flag-junit",
"frameworks-base-testutils",
"platform-compat-test-rules",
diff --git a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 0e815d0eab95..3e731a322d4e 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -163,6 +163,7 @@ public class LowPowerStandbyControllerTest {
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(true);
when(mDeviceConfigWrapperMock.enableCustomPolicy()).thenReturn(true);
when(mDeviceConfigWrapperMock.enableStandbyPorts()).thenReturn(true);
@@ -899,11 +900,13 @@ public class LowPowerStandbyControllerTest {
private void setInteractive() throws Exception {
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(true);
mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
}
private void setNonInteractive() throws Exception {
when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+ when(mIPowerManagerMock.isDisplayInteractive(anyInt())).thenReturn(false);
mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index fc4d8d871fd5..96741e0b1e87 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -16,13 +16,20 @@
package com.android.server.power;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -31,12 +38,15 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManagerInternal;
import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
import android.os.Handler;
import android.os.IWakeLockCallback;
import android.os.Looper;
@@ -45,21 +55,33 @@ import android.os.RemoteException;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.testing.TestableContext;
+import android.util.IntArray;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
import androidx.test.InstrumentationRegistry;
import com.android.internal.app.IBatteryStats;
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.power.FrameworkStatsLogger.WakelockEventType;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -71,6 +93,16 @@ import java.util.concurrent.Executor;
public class NotifierTest {
private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
private static final int USER_ID = 0;
+ private static final int DISPLAY_PORT = 0xFF;
+ private static final long DISPLAY_MODEL = 0xEEEEEEEEL;
+
+ private static final int UID = 1234;
+ private static final int OWNER_UID = 1235;
+ private static final int WORK_SOURCE_UID_1 = 2345;
+ private static final int WORK_SOURCE_UID_2 = 2346;
+ private static final int OWNER_WORK_SOURCE_UID_1 = 3456;
+ private static final int OWNER_WORK_SOURCE_UID_2 = 3457;
+ private static final int PID = 5678;
@Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
@Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
@@ -81,20 +113,30 @@ public class NotifierTest {
@Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@Mock private Vibrator mVibrator;
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+ @Mock private InputManagerInternal mInputManagerInternal;
+ @Mock private InputMethodManagerInternal mInputMethodManagerInternal;
+ @Mock private DisplayManagerInternal mDisplayManagerInternal;
+ @Mock private ActivityManagerInternal mActivityManagerInternal;
@Mock private WakeLockLog mWakeLockLog;
@Mock private IBatteryStats mBatteryStats;
+ @Mock private WindowManagerPolicy mPolicy;
+
@Mock private PowerManagerFlags mPowerManagerFlags;
@Mock private AppOpsManager mAppOpsManager;
+ @Mock private BatteryStatsInternal mBatteryStatsInternal;
+ @Mock private FrameworkStatsLogger mLogger;
+
private PowerManagerService mService;
private Context mContextSpy;
private Resources mResourcesSpy;
private TestLooper mTestLooper = new TestLooper();
private FakeExecutor mTestExecutor = new FakeExecutor();
private Notifier mNotifier;
+ private DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
@Before
public void setUp() {
@@ -103,11 +145,25 @@ public class NotifierTest {
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+ LocalServices.removeServiceForTest(InputManagerInternal.class);
+ LocalServices.addService(InputManagerInternal.class, mInputManagerInternal);
+ LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
+ LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
+
+ mDefaultDisplayInfo.address = DisplayAddress.fromPortAndModel(DISPLAY_PORT, DISPLAY_MODEL);
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+
mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+ when(mDisplayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+ mDefaultDisplayInfo);
mService = new PowerManagerService(mContextSpy, mInjector);
}
@@ -232,6 +288,388 @@ public class NotifierTest {
}
@Test
+ public void testOnGlobalWakefulnessChangeStarted() {
+ createNotifier();
+ // GIVEN system is currently non-interactive
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(false);
+ final int displayId1 = 101;
+ final int displayId2 = 102;
+ final int[] displayIds = new int[]{displayId1, displayId2};
+ when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displayIds));
+ mNotifier.onGlobalWakefulnessChangeStarted(WAKEFULNESS_ASLEEP,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, /* eventTime= */ 1000);
+ mTestLooper.dispatchAll();
+
+ // WHEN a global wakefulness change to interactive starts
+ mNotifier.onGlobalWakefulnessChangeStarted(WAKEFULNESS_AWAKE,
+ PowerManager.WAKE_REASON_TAP, /* eventTime= */ 2000);
+ mTestLooper.dispatchAll();
+
+ // THEN input is notified of all displays being interactive
+ final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray();
+ expectedDisplayInteractivities.put(displayId1, true);
+ expectedDisplayInteractivities.put(displayId2, true);
+ verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities);
+ verify(mInputMethodManagerInternal).setInteractive(/* interactive= */ true);
+ }
+
+ @Test
+ public void testOnGroupWakefulnessChangeStarted_newPowerGroup_perDisplayWakeDisabled() {
+ createNotifier();
+ // GIVEN power group is not yet known to Notifier and per-display wake by touch is disabled
+ final int groupId = 123;
+ final int changeReason = PowerManager.WAKE_REASON_TAP;
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(false);
+
+ // WHEN a power group wakefulness change starts
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_AWAKE, changeReason, /* eventTime= */ 999);
+ mTestLooper.dispatchAll();
+
+ // THEN window manager policy is informed that device has started waking up
+ verify(mPolicy).startedWakingUp(groupId, changeReason);
+ verify(mDisplayManagerInternal, never()).getDisplayIds();
+ verify(mInputManagerInternal, never()).setDisplayInteractivities(any());
+ }
+
+ @Test
+ public void testOnGroupWakefulnessChangeStarted_interactivityNoChange_perDisplayWakeDisabled() {
+ createNotifier();
+ // GIVEN power group is not interactive and per-display wake by touch is disabled
+ final int groupId = 234;
+ final int changeReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(false);
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_ASLEEP, changeReason, /* eventTime= */ 999);
+ mTestLooper.dispatchAll();
+ verify(mPolicy, times(1)).startedGoingToSleep(groupId, changeReason);
+
+ // WHEN a power wakefulness change to not interactive starts
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_ASLEEP, changeReason, /* eventTime= */ 999);
+ mTestLooper.dispatchAll();
+
+ // THEN policy is only informed once of non-interactive wakefulness change
+ verify(mPolicy, times(1)).startedGoingToSleep(groupId, changeReason);
+ verify(mDisplayManagerInternal, never()).getDisplayIds();
+ verify(mInputManagerInternal, never()).setDisplayInteractivities(any());
+ }
+
+ @Test
+ public void testOnGroupWakefulnessChangeStarted_interactivityChange_perDisplayWakeDisabled() {
+ createNotifier();
+ // GIVEN power group is not interactive and per-display wake by touch is disabled
+ final int groupId = 345;
+ final int firstChangeReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(false);
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_ASLEEP, firstChangeReason, /* eventTime= */ 999);
+ mTestLooper.dispatchAll();
+
+ // WHEN a power wakefulness change to interactive starts
+ final int secondChangeReason = PowerManager.WAKE_REASON_TAP;
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_AWAKE, secondChangeReason, /* eventTime= */ 999);
+ mTestLooper.dispatchAll();
+
+ // THEN policy is informed of the change
+ verify(mPolicy).startedWakingUp(groupId, secondChangeReason);
+ verify(mDisplayManagerInternal, never()).getDisplayIds();
+ verify(mInputManagerInternal, never()).setDisplayInteractivities(any());
+ }
+
+ @Test
+ public void testOnGroupWakefulnessChangeStarted_perDisplayWakeByTouchEnabled() {
+ createNotifier();
+ // GIVEN per-display wake by touch flag is enabled
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true);
+ final int groupId = 456;
+ final int displayId1 = 1001;
+ final int displayId2 = 1002;
+ final int[] displays = new int[]{displayId1, displayId2};
+ when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays));
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays);
+ final int changeReason = PowerManager.WAKE_REASON_TAP;
+
+ // WHEN power group wakefulness change started
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_AWAKE, changeReason, /* eventTime= */ 999);
+ mTestLooper.dispatchAll();
+
+ // THEN native input manager is updated that the displays are interactive
+ final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray();
+ expectedDisplayInteractivities.put(displayId1, true);
+ expectedDisplayInteractivities.put(displayId2, true);
+ verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities);
+ }
+
+ @Test
+ public void testOnGroupRemoved_perDisplayWakeByTouchEnabled() {
+ createNotifier();
+ // GIVEN per-display wake by touch is enabled and one display group has been defined
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true);
+ final int groupId = 313;
+ final int displayId1 = 3113;
+ final int displayId2 = 4114;
+ final int[] displays = new int[]{displayId1, displayId2};
+ when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays));
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays);
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_TAP, /* eventTime= */ 1000);
+ final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray();
+ expectedDisplayInteractivities.put(displayId1, true);
+ expectedDisplayInteractivities.put(displayId2, true);
+ verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities);
+
+ // WHEN display group is removed
+ when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(new SparseArray<>());
+ mNotifier.onGroupRemoved(groupId);
+
+ // THEN native input manager is informed that displays in that group no longer exist
+ verify(mInputManagerInternal).setDisplayInteractivities(new SparseBooleanArray());
+ }
+
+ @Test
+ public void testOnWakeLockReleased_FrameworkStatsLogged_NoChains() {
+ when(mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()).thenReturn(true);
+ createNotifier();
+
+ clearInvocations(mLogger, mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ when(mBatteryStatsInternal.getOwnerUid(UID)).thenReturn(OWNER_UID);
+ when(mBatteryStatsInternal.getOwnerUid(WORK_SOURCE_UID_1))
+ .thenReturn(OWNER_WORK_SOURCE_UID_1);
+
+ mNotifier.onWakeLockAcquired(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "wakelockTag",
+ "my.package.name",
+ UID,
+ PID,
+ /* workSource= */ null,
+ /* historyTag= */ null,
+ /* callback= */ null);
+
+ WorkSource ws = new WorkSource(WORK_SOURCE_UID_1);
+
+ mNotifier.onWakeLockChanging(
+ /* existing WakeLock params */
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "wakelockTag",
+ "my.package.name",
+ UID,
+ PID,
+ /* workSource= */ null,
+ /* historyTag= */ null,
+ /* callback= */ null,
+ /* updated WakeLock params */
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "wakelockTag",
+ "my.package.name",
+ UID,
+ PID,
+ ws,
+ /* historyTag= */ null,
+ /* callback= */ null);
+
+ mNotifier.onWakeLockReleased(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "wakelockTag",
+ "my.package.name",
+ UID,
+ PID,
+ ws,
+ /* historyTag= */ null,
+ /* callback= */ null);
+
+ verify(mBatteryStatsInternal, atLeast(1)).getOwnerUid(eq(UID));
+ verify(mBatteryStatsInternal, atLeast(1)).getOwnerUid(eq(WORK_SOURCE_UID_1));
+
+ // ACQUIRE before RELEASE
+ InOrder inOrder1 = inOrder(mLogger);
+ inOrder1.verify(mLogger)
+ .wakelockStateChanged(
+ eq(OWNER_UID),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ inOrder1.verify(mLogger)
+ .wakelockStateChanged(
+ eq(OWNER_UID),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+
+ InOrder inOrder2 = inOrder(mLogger);
+ inOrder2.verify(mLogger)
+ .wakelockStateChanged(
+ eq(OWNER_WORK_SOURCE_UID_1),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ inOrder2.verify(mLogger)
+ .wakelockStateChanged(
+ eq(OWNER_WORK_SOURCE_UID_1),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ }
+
+ @Test
+ public void testOnWakeLockReleased_FrameworkStatsLogged_MultipleWorkSourceUids() {
+ // UIDs stored directly in WorkSource
+ WorkSource ws = new WorkSource(WORK_SOURCE_UID_1);
+ ws.add(WORK_SOURCE_UID_2);
+ testWorkSource(ws);
+
+ InOrder inOrder = inOrder(mLogger);
+ ArgumentCaptor<Integer> captorInt = ArgumentCaptor.forClass(int.class);
+
+ // ACQUIRE
+ inOrder.verify(mLogger, times(2))
+ .wakelockStateChanged(
+ /* uid= */ captorInt.capture(),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ assertThat(captorInt.getAllValues())
+ .containsExactly(OWNER_WORK_SOURCE_UID_1, OWNER_WORK_SOURCE_UID_2);
+
+ // RELEASE
+ captorInt = ArgumentCaptor.forClass(int.class);
+ inOrder.verify(mLogger, times(2))
+ .wakelockStateChanged(
+ /* uid= */ captorInt.capture(),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ assertThat(captorInt.getAllValues())
+ .containsExactly(OWNER_WORK_SOURCE_UID_1, OWNER_WORK_SOURCE_UID_2);
+ }
+
+ @Test
+ public void testOnWakeLockReleased_FrameworkStatsLogged_OneChain() {
+ // UIDs stored in a WorkChain of the WorkSource
+ WorkSource ws = new WorkSource();
+ WorkChain wc = ws.createWorkChain();
+ wc.addNode(WORK_SOURCE_UID_1, "tag1");
+ wc.addNode(WORK_SOURCE_UID_2, "tag2");
+ testWorkSource(ws);
+
+ WorkChain expectedWorkChain = new WorkChain();
+ expectedWorkChain.addNode(OWNER_WORK_SOURCE_UID_1, "tag1");
+ expectedWorkChain.addNode(OWNER_WORK_SOURCE_UID_2, "tag2");
+
+ InOrder inOrder = inOrder(mLogger);
+
+ // ACQUIRE
+ inOrder.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ // RELEASE
+ inOrder.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ }
+
+ @Test
+ public void testOnWakeLockReleased_FrameworkStatsLogged_OneUid_OneChain() {
+ WorkSource ws = new WorkSource(WORK_SOURCE_UID_1);
+ WorkChain wc = ws.createWorkChain();
+ wc.addNode(WORK_SOURCE_UID_2, "someTag");
+ testWorkSource(ws);
+
+ WorkChain expectedWorkChain = new WorkChain();
+ expectedWorkChain.addNode(OWNER_WORK_SOURCE_UID_2, "someTag");
+
+ InOrder inOrder1 = inOrder(mLogger);
+ InOrder inOrder2 = inOrder(mLogger);
+
+ // ACQUIRE
+ inOrder1.verify(mLogger)
+ .wakelockStateChanged(
+ eq(OWNER_WORK_SOURCE_UID_1),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ inOrder2.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ // RELEASE
+ inOrder1.verify(mLogger)
+ .wakelockStateChanged(
+ eq(OWNER_WORK_SOURCE_UID_1),
+ eq("wakelockTag"),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ inOrder2.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ }
+
+ @Test
+ public void testOnWakeLockReleased_FrameworkStatsLogged_TwoChains() {
+ // UIDs stored in a WorkChain of the WorkSource
+ WorkSource ws = new WorkSource();
+ WorkChain wc1 = ws.createWorkChain();
+ wc1.addNode(WORK_SOURCE_UID_1, "tag1");
+
+ WorkChain wc2 = ws.createWorkChain();
+ wc2.addNode(WORK_SOURCE_UID_2, "tag2");
+
+ testWorkSource(ws);
+
+ WorkChain expectedWorkChain1 = new WorkChain();
+ expectedWorkChain1.addNode(OWNER_WORK_SOURCE_UID_1, "tag1");
+
+ WorkChain expectedWorkChain2 = new WorkChain();
+ expectedWorkChain2.addNode(OWNER_WORK_SOURCE_UID_2, "tag2");
+
+ InOrder inOrder1 = inOrder(mLogger);
+ InOrder inOrder2 = inOrder(mLogger);
+
+ // ACQUIRE
+ inOrder1.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain1),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+ inOrder2.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain2),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.ACQUIRE));
+
+ // RELEASE
+ inOrder1.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain1),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ inOrder2.verify(mLogger)
+ .wakelockStateChanged(
+ eq("wakelockTag"),
+ eq(expectedWorkChain2),
+ eq(PowerManager.PARTIAL_WAKE_LOCK),
+ eq(WakelockEventType.RELEASE));
+ }
+
+ @Test
public void testOnWakeLockListener_RemoteException_NoRethrow() throws RemoteException {
when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
createNotifier();
@@ -529,29 +967,40 @@ public class NotifierTest {
}
private void createNotifier() {
- Notifier.Injector injector = new Notifier.Injector() {
- @Override
- public long currentTimeMillis() {
- return 1;
- }
-
- @Override
- public WakeLockLog getWakeLockLog(Context context) {
- return mWakeLockLog;
- }
-
- @Override
- public AppOpsManager getAppOpsManager(Context context) {
- return mAppOpsManager;
- }
- };
+ Notifier.Injector injector =
+ new Notifier.Injector() {
+ @Override
+ public long currentTimeMillis() {
+ return 1;
+ }
+
+ @Override
+ public WakeLockLog getWakeLockLog(Context context) {
+ return mWakeLockLog;
+ }
+
+ @Override
+ public AppOpsManager getAppOpsManager(Context context) {
+ return mAppOpsManager;
+ }
+
+ @Override
+ public FrameworkStatsLogger getFrameworkStatsLogger() {
+ return mLogger;
+ }
+
+ @Override
+ public BatteryStatsInternal getBatteryStatsInternal() {
+ return mBatteryStatsInternal;
+ }
+ };
mNotifier = new Notifier(
mTestLooper.getLooper(),
mContextSpy,
mBatteryStats,
mInjector.createSuspendBlocker(mService, "testBlocker"),
- null,
+ mPolicy,
null,
null,
mTestExecutor, mPowerManagerFlags, injector);
@@ -581,4 +1030,38 @@ public class NotifierTest {
}
}
+ private void testWorkSource(WorkSource ws) {
+ when(mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()).thenReturn(true);
+ createNotifier();
+ clearInvocations(
+ mBatteryStatsInternal, mLogger, mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ when(mBatteryStatsInternal.getOwnerUid(WORK_SOURCE_UID_1))
+ .thenReturn(OWNER_WORK_SOURCE_UID_1);
+ when(mBatteryStatsInternal.getOwnerUid(WORK_SOURCE_UID_2))
+ .thenReturn(OWNER_WORK_SOURCE_UID_2);
+
+ mNotifier.onWakeLockAcquired(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "wakelockTag",
+ "my.package.name",
+ UID,
+ PID,
+ ws,
+ /* historyTag= */ null,
+ /* callback= */ null);
+
+ mNotifier.onWakeLockReleased(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "wakelockTag",
+ "my.package.name",
+ UID,
+ PID,
+ ws,
+ /* historyTag= */ null,
+ /* callback= */ null);
+
+ verify(mBatteryStatsInternal, atLeast(1)).getOwnerUid(eq(WORK_SOURCE_UID_1));
+ verify(mBatteryStatsInternal, atLeast(1)).getOwnerUid(eq(WORK_SOURCE_UID_2));
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 473c8c5ce8fc..4e29e74651b6 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -27,10 +27,13 @@ import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD;
import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
import static android.os.PowerManager.WAKE_REASON_GESTURE;
import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.view.Display.STATE_REASON_DEFAULT_POLICY;
+import static android.view.Display.STATE_REASON_MOTION;
import static com.android.server.power.PowerManagerService.USER_ACTIVITY_SCREEN_BRIGHT;
import static com.android.server.power.PowerManagerService.WAKE_LOCK_DOZE;
@@ -44,17 +47,25 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
import android.os.PowerSaveState;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.power.feature.PowerManagerFlags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -68,6 +79,9 @@ import org.mockito.MockitoAnnotations;
public class PowerGroupTest {
private static final int GROUP_ID = 0;
+ private static final int NON_DEFAULT_GROUP_ID = 1;
+ private static final int NON_DEFAULT_DISPLAY_ID = 2;
+ private static final int VIRTUAL_DEVICE_ID = 3;
private static final int UID = 11;
private static final long TIMESTAMP_CREATE = 1;
private static final long TIMESTAMP1 = 999;
@@ -79,18 +93,29 @@ public class PowerGroupTest {
private static final float BRIGHTNESS = 0.99f;
private static final float BRIGHTNESS_DOZE = 0.5f;
+ private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ private static final long DEFAULT_TIMEOUT = 1234L;
+
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private PowerGroup mPowerGroup;
@Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
@Mock private Notifier mNotifier;
@Mock private DisplayManagerInternal mDisplayManagerInternal;
+ @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+ @Mock private PowerManagerFlags mFeatureFlags;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(true);
mPowerGroup = new PowerGroup(GROUP_ID, mWakefulnessCallbackMock, mNotifier,
mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
- /* supportsSandman= */ true, TIMESTAMP_CREATE);
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
}
@Test
@@ -101,10 +126,8 @@ public class PowerGroupTest {
eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
isNull());
String details = "wake PowerGroup1";
- LatencyTracker latencyTracker = LatencyTracker.getInstance(
- InstrumentationRegistry.getInstrumentation().getContext());
mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, details, UID,
- /* opPackageName= */ null, /* opUid= */ 0, latencyTracker);
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_PLUGGED_IN), eq(UID),
/* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
@@ -247,6 +270,10 @@ public class PowerGroupTest {
@Test
public void testUpdateWhileAwake_UpdatesDisplayPowerRequest() {
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
final boolean batterySaverEnabled = true;
float brightnessFactor = 0.7f;
PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -274,6 +301,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_MOTION);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.screenBrightnessOverrideTag.toString()).isEqualTo(tag);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
@@ -288,6 +316,55 @@ public class PowerGroupTest {
}
@Test
+ public void testWakefulnessReasonInDisplayPowerRequestDisabled_wakefulnessReasonNotPopulated() {
+ when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(false);
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
+ mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ "my/tag",
+ /* useProximitySensor= */ false,
+ /* boostScreenBrightness= */ false,
+ /* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+ /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+ /* useNormalBrightnessForDoze= */ false,
+ /* overrideDrawWakeLock= */ false,
+ new PowerSaveState.Builder().build(),
+ /* quiescent= */ false,
+ /* dozeAfterScreenOff= */ false,
+ /* bootCompleted= */ true,
+ /* screenBrightnessBoostInProgress= */ false,
+ /* waitForNegativeProximity= */ false,
+ /* brightWhenDozing= */ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ mPowerGroup.mDisplayPowerRequest;
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+ mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ "my/tag",
+ /* useProximitySensor= */ false,
+ /* boostScreenBrightness= */ false,
+ /* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+ /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+ /* useNormalBrightnessForDoze= */ false,
+ /* overrideDrawWakeLock= */ false,
+ new PowerSaveState.Builder().build(),
+ /* quiescent= */ false,
+ /* dozeAfterScreenOff= */ false,
+ /* bootCompleted= */ true,
+ /* screenBrightnessBoostInProgress= */ false,
+ /* waitForNegativeProximity= */ false,
+ /* brightWhenDozing= */ false);
+ displayPowerRequest = mPowerGroup.mDisplayPowerRequest;
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+ }
+
+ @Test
public void testUpdateWhileDozing_UpdatesDisplayPowerRequest() {
final boolean useNormalBrightnessForDoze = false;
final boolean batterySaverEnabled = false;
@@ -319,6 +396,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -363,6 +441,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -405,6 +484,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -419,6 +499,10 @@ public class PowerGroupTest {
@Test
public void testUpdateQuiescent() {
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
final boolean batterySaverEnabled = false;
float brightnessFactor = 0.3f;
PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -446,6 +530,11 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+ // Note how the reason is STATE_REASON_DEFAULT_POLICY, instead of STATE_REASON_MOTION.
+ // This is because - although there was a wake up request from a motion, the quiescent state
+ // preceded and forced the policy to be OFF, so we ignore the reason associated with the
+ // wake up request.
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -487,6 +576,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -529,6 +619,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -569,6 +660,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -610,6 +702,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -650,6 +743,7 @@ public class PowerGroupTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -661,4 +755,111 @@ public class PowerGroupTest {
assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
brightnessFactor);
}
+
+ @Test
+ public void testTimeoutsOverride_defaultGroup_noOverride() {
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_noVdm_noOverride() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, null);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_notValidVirtualDeviceId_noOverride() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(Context.DEVICE_ID_DEFAULT);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(Context.DEVICE_ID_DEFAULT))
+ .thenReturn(false);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_validVirtualDeviceId_timeoutsAreOverridden() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ final long dimDurationOverride = DEFAULT_TIMEOUT * 3;
+ final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 5;
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(VIRTUAL_DEVICE_ID);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(dimDurationOverride);
+ when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(screenOffTimeoutOverride);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(dimDurationOverride);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverrides_dimDurationIsCapped() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ final long dimDurationOverride = DEFAULT_TIMEOUT * 5;
+ final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 3;
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(VIRTUAL_DEVICE_ID);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(dimDurationOverride);
+ when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(screenOffTimeoutOverride);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b58c28bfbe62..376091e4a241 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -70,7 +70,6 @@ import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.hardware.power.Boost;
import android.hardware.power.Mode;
import android.os.BatteryManager;
@@ -86,6 +85,7 @@ import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -103,6 +103,7 @@ import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
@@ -168,6 +169,7 @@ public class PowerManagerServiceTest {
@Mock private BatteryManagerInternal mBatteryManagerInternalMock;
@Mock private ActivityManagerInternal mActivityManagerInternalMock;
@Mock private AttentionManagerInternal mAttentionManagerInternalMock;
+ @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
@Mock private DreamManagerInternal mDreamManagerInternalMock;
@Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
@Mock private FoldGracePeriodProvider mFoldGracePeriodProvider;
@@ -247,6 +249,7 @@ public class PowerManagerServiceTest {
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+ addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResourcesSpy = spy(mContextSpy.getResources());
@@ -268,6 +271,10 @@ public class PowerManagerServiceTest {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
+ DisplayInfo displayInfo = Mockito.mock(DisplayInfo.class);
+ displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY))
+ .thenReturn(displayInfo);
}
private PowerManagerService createService() {
@@ -794,6 +801,57 @@ public class PowerManagerServiceTest {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
}
+ @Test
+ public void testWakefulnessPerGroup_IPowerManagerWakeUpWithDisplayId() {
+ final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ int displayInNonDefaultGroup = 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ long eventTime1 = 10;
+ long eventTime2 = eventTime1 + 1;
+ long eventTime3 = eventTime2 + 1;
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId);
+
+ // Verify the global wakefulness is AWAKE
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Transition default display to doze, and verify the global wakefulness
+ mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1,
+ 0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Transition the display from non default power group to doze, and verify the change in
+ // the global wakefulness
+ mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2,
+ 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ assertThat(mService.getWakefulnessLocked(nonDefaultPowerGroupId))
+ .isEqualTo(WAKEFULNESS_DOZING);
+
+ // Wakeup the display from the non default power group
+ DisplayInfo displayInfo = Mockito.mock(DisplayInfo.class);
+ displayInfo.displayGroupId = nonDefaultPowerGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(displayInNonDefaultGroup))
+ .thenReturn(displayInfo);
+ mClock.fastForward(eventTime3);
+ mService.getBinderServiceInstance().wakeUpWithDisplayId(eventTime3,
+ PowerManager.WAKE_REASON_APPLICATION, "testing IPowerManager.wakeUp()",
+ "pkg.name", displayInNonDefaultGroup);
+
+ assertThat(mService.getWakefulnessLocked(nonDefaultPowerGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY))
+ .isEqualTo(WAKEFULNESS_DOZING);
+ }
+
/**
* Tests a series of variants that control whether a device wakes-up when it is plugged in
* or docked.
@@ -1146,6 +1204,89 @@ public class PowerManagerServiceTest {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testNonDefaultDisplayGroupWithCustomTimeout_afterTimeout_goesToDozing() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 2;
+ final int virtualDeviceId = Context.DEVICE_ID_DEFAULT + 3;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+ when(mDisplayManagerInternalMock.getDisplayIdsForGroup(nonDefaultDisplayGroupId))
+ .thenReturn(new int[] {nonDefaultDisplayId});
+ when(mVirtualDeviceManagerInternalMock.getDeviceIdForDisplayId(nonDefaultDisplayId))
+ .thenReturn(virtualDeviceId);
+ when(mVirtualDeviceManagerInternalMock.isValidVirtualDeviceId(virtualDeviceId))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternalMock
+ .getScreenOffTimeoutMillisForDeviceId(virtualDeviceId))
+ .thenReturn(20000L);
+
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+
+ // The default timeout is 10s, the custom group timeout is 20s.
+
+ advanceTime(15000);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+
+ advanceTime(10000);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_DOZING);
+ }
+
+ @EnableFlags({
+ android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER,
+ android.companion.virtualdevice.flags.Flags.FLAG_DISPLAY_POWER_MANAGER_APIS})
+ @Test
+ public void getBrightnessConstraint_valuesMatchDisplayInfo() {
+ final int displayId = 7;
+ final DisplayInfo info = new DisplayInfo();
+ info.brightnessMinimum = 0.12f;
+ info.brightnessDim = 0.34f;
+ info.brightnessDefault = 0.56f;
+ info.brightnessMaximum = 0.78f;
+ when(mDisplayManagerInternalMock.getDisplayInfo(displayId)).thenReturn(info);
+
+ createService();
+ startSystem();
+
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM))
+ .isEqualTo(info.brightnessMinimum);
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM))
+ .isEqualTo(info.brightnessMaximum);
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT))
+ .isEqualTo(info.brightnessDefault);
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM))
+ .isEqualTo(info.brightnessDim);
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
@@ -1604,6 +1745,64 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testIsWakeLockLevelSupported_returnsCorrectValue() {
+ createService();
+ startSystem();
+ PowerManagerService.BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.FULL_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.DOZE_WAKE_LOCK)).isTrue();
+ assertThat(service.isWakeLockLevelSupported(PowerManager.DRAW_WAKE_LOCK)).isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(true);
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(false);
+ assertThat(service.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsWakeLockLevelSupportedWithDisplayId_nonDefaultDisplay_returnsCorrectValue() {
+ createService();
+ startSystem();
+ int displayId = Display.DEFAULT_DISPLAY + 1;
+ PowerManagerService.BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PARTIAL_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.FULL_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.DOZE_WAKE_LOCK, displayId))
+ .isTrue();
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.DRAW_WAKE_LOCK, displayId))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(displayId)))
+ .thenReturn(true);
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, displayId))
+ .isTrue();
+
+ when(mDisplayManagerInternalMock.isProximitySensorAvailable(eq(displayId)))
+ .thenReturn(false);
+ assertThat(service.isWakeLockLevelSupportedWithDisplayId(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, displayId))
+ .isFalse();
+ }
+
+
+ @Test
public void testWakeLock_affectsProperDisplayGroup() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -1644,6 +1843,47 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testWakeLock_nonDefaultDisplay_affectsProperDisplayGroup() {
+ final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+
+ final String pkg = mContextSpy.getOpPackageName();
+ final Binder token = new Binder();
+ final String tag = "testWakeLock_nonDefaultDisplay_affectsProperDisplayGroup";
+
+ setMinimumScreenOffTimeoutConfig(5);
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ mService.getBinderServiceInstance().acquireWakeLock(token,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+ null /* workSource */, null /* historyTag */, nonDefaultDisplayId, null);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+
+ advanceTime(15000);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ }
+
+ @Test
public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -1740,15 +1980,6 @@ public class PowerManagerServiceTest {
}
@Test
- public void testBoot_DesiredScreenPolicyShouldBeBright() {
- createService();
- startSystem();
-
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
public void testQuiescentBoot_ShouldBeAsleep() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
@@ -1760,40 +1991,6 @@ public class PowerManagerServiceTest {
}
@Test
- public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
- createService();
- startSystem();
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_OFF);
- }
-
- @Test
- public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
- createService();
- startSystem();
- forceAwake();
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
- public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
- createService();
- startSystem();
-
- mService.getBinderServiceInstance().wakeUp(mClock.now(),
- PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
public void testIsAmbientDisplayAvailable_available() {
createService();
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
@@ -2535,10 +2732,10 @@ public class PowerManagerServiceTest {
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
- listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
verify(mInvalidateInteractiveCachesMock).call();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
@@ -2561,10 +2758,10 @@ public class PowerManagerServiceTest {
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
- listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
verify(mInvalidateInteractiveCachesMock).call();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
@@ -3236,7 +3433,8 @@ public class PowerManagerServiceTest {
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
null /* callback */);
}
- assertThat(mService.getScreenOffTimeoutOverrideLocked(screenTimeout, screenDimTimeout))
+ assertThat(mService.getDefaultGroupScreenOffTimeoutOverrideLocked(screenTimeout,
+ screenDimTimeout))
.isEqualTo(expect[2]);
if (acquireWakeLock) {
mService.getBinderServiceInstance().releaseWakeLock(token, 0);
diff --git a/services/tests/powerstatstests/res/xml/irq_device_map_3.xml b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
index fd55428c48df..c3df0785bd9b 100644
--- a/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
+++ b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
@@ -32,4 +32,7 @@
<device name="test.sensor.device">
<subsystem>Sensor</subsystem>
</device>
+ <device name="test.bluetooth.device">
+ <subsystem>Bluetooth</subsystem>
+ </device>
</irq-device-map> \ No newline at end of file
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
index c0be8652f303..4b91d8418fc3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
@@ -79,8 +79,6 @@ public class AmbientDisplayPowerCalculatorTest {
// 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(27.777778);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -140,8 +138,6 @@ public class AmbientDisplayPowerCalculatorTest {
// (seconds/hour) = 27.777778 mAh
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(83.33333);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -163,8 +159,6 @@ public class AmbientDisplayPowerCalculatorTest {
.isEqualTo(90 * MINUTE_IN_MS);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(15.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -195,7 +189,5 @@ public class AmbientDisplayPowerCalculatorTest {
.isEqualTo(120 * MINUTE_IN_MS);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(35.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 7b635d4198c3..2b152315eec4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -39,7 +39,6 @@ import android.hardware.power.stats.StateResidencyResult;
import android.os.Handler;
import android.os.Looper;
import android.os.connectivity.WifiActivityEnergyInfo;
-import android.platform.test.ravenwood.RavenwoodRule;
import android.power.PowerStatsInternal;
import android.util.IntArray;
import android.util.SparseArray;
@@ -52,7 +51,6 @@ import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import java.util.Arrays;
@@ -67,25 +65,24 @@ import java.util.concurrent.CompletableFuture;
@SuppressWarnings("GuardedBy")
@android.platform.test.annotations.DisabledOnRavenwood
public class BatteryExternalStatsWorkerTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
private TestPowerStatsInternal mPowerStatsInternal;
+ private Handler mHandler;
@Before
public void setUp() {
final Context context = InstrumentationRegistry.getContext();
+ mHandler = new Handler(Looper.getMainLooper());
BatteryStatsImpl batteryStats = new BatteryStatsImpl(
new BatteryStatsImpl.BatteryStatsConfig.Builder().build(), Clock.SYSTEM_CLOCK,
new MonotonicClock(0, Clock.SYSTEM_CLOCK), null,
- new Handler(Looper.getMainLooper()), null, null, null,
+ mHandler, null, null, null,
new PowerProfile(context, true /* forTest */), buildScalingPolicies(),
new PowerStatsUidResolver());
mPowerStatsInternal = new TestPowerStatsInternal();
mBatteryExternalStatsWorker =
- new BatteryExternalStatsWorker(new TestInjector(context), batteryStats);
+ new BatteryExternalStatsWorker(new TestInjector(context), batteryStats, mHandler);
}
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index d36b55345418..d6f50368df84 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -38,7 +38,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
-import java.util.concurrent.Future;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -324,9 +323,8 @@ public class BatteryStatsHistoryIteratorTest {
private boolean mSyncScheduled;
@Override
- public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
+ public void scheduleCpuSyncDueToWakelockChange(long delayMillis) {
mSyncScheduled = true;
- return null;
}
public boolean isSyncScheduled() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index e40a3e314e58..b67ec8b2c828 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -653,6 +653,44 @@ public class BatteryStatsHistoryTest {
}
@Test
+ public void getMonotonicHistorySize() {
+ long lastHistorySize = mHistory.getMonotonicHistorySize();
+ mHistory.forceRecordAllHistory();
+
+ mClock.realtime = 1000;
+ mClock.uptime = 1000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
+ long size = mHistory.getMonotonicHistorySize();
+ assertThat(size).isGreaterThan(lastHistorySize);
+ lastHistorySize = size;
+
+ mHistory.startNextFile(mClock.realtime);
+
+ size = mHistory.getMonotonicHistorySize();
+ assertThat(size).isEqualTo(lastHistorySize);
+
+ mClock.realtime = 2000;
+ mClock.uptime = 2000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
+
+ size = mHistory.getMonotonicHistorySize();
+ assertThat(size).isGreaterThan(lastHistorySize);
+ lastHistorySize = size;
+
+ mHistory.startNextFile(mClock.realtime);
+
+ mClock.realtime = 3000;
+ mClock.uptime = 3000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ HistoryItem.EVENT_ALARM, "alarm", 42);
+
+ size = mHistory.getMonotonicHistorySize();
+ assertThat(size).isGreaterThan(lastHistorySize);
+ }
+
+ @Test
public void testVarintParceler() {
long[] values = {
0,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 1d20538724a8..d83dc110800a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -54,6 +54,8 @@ import android.os.HandlerThread;
import android.os.Parcel;
import android.os.WakeLockStats;
import android.os.WorkSource;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
import android.view.Display;
@@ -65,7 +67,9 @@ import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
import com.android.internal.os.KernelSingleUidTimeReader;
import com.android.internal.os.LongArrayMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
+import com.android.server.power.feature.flags.Flags;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.LongSubject;
@@ -86,11 +90,16 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
@SuppressWarnings("GuardedBy")
public class BatteryStatsImplTest {
- @Rule
+ @Rule(order = 0)
public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
.setProvideMainThread(true)
+ .setSystemPropertyImmutable("persist.sys.com.android.server.power.feature.flags."
+ + "framework_wakelock_info-override", null)
.build();
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
@Mock
@@ -112,6 +121,7 @@ public class BatteryStatsImplTest {
}});
private final MockClock mMockClock = new MockClock();
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(777666, mMockClock);
private MockBatteryStatsImpl mBatteryStatsImpl;
private Handler mHandler;
private PowerStatsStore mPowerStatsStore;
@@ -151,8 +161,8 @@ public class BatteryStatsImplTest {
}
mPowerStatsStore = new PowerStatsStore(systemDir, mHandler);
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerAttributor,
- mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore,
- mMockClock);
+ mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore, 0,
+ mMockClock, mMonotonicClock);
}
@Test
@@ -430,10 +440,7 @@ public class BatteryStatsImplTest {
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
+ counter.updateValues(cpuTimes, timestampMs);
return null;
}).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
any(LongArrayMultiStateCounter.class), anyLong());
@@ -443,20 +450,13 @@ public class BatteryStatsImplTest {
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- invocation.getArgument(3);
-
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
- if (deltaContainer != null) {
- deltaContainer.setValues(delta);
- }
+ long[] deltaOut = invocation.getArgument(3);
+ counter.updateValues(cpuTimes, timestampMs);
+ System.arraycopy(delta, 0, deltaOut, 0, delta.length);
return null;
}).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
any(LongArrayMultiStateCounter.class), anyLong(),
- any(LongArrayMultiStateCounter.LongArrayContainer.class));
+ any(long[].class));
}
@Test
@@ -572,6 +572,7 @@ public class BatteryStatsImplTest {
}
@Test
+ @EnableFlags(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO)
public void testGetWakeLockStats() {
mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
@@ -927,7 +928,7 @@ public class BatteryStatsImplTest {
assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
mBatteryStatsImpl.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider,
- mPowerStatsStore);
+ mPowerStatsStore, /* accumulateBatteryUsageStats */ false);
synchronized (mBatteryStatsImpl) {
mBatteryStatsImpl.noteFlashlightOnLocked(42, mMockClock.realtime, mMockClock.uptime);
@@ -978,5 +979,7 @@ public class BatteryStatsImplTest {
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isEqualTo(60000);
+
+ span.close();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
index 9a64ce19254b..94997b29cdd5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -41,7 +41,7 @@ public class BatteryStatsManagerTest {
public final RavenwoodRule mRavenwood = new RavenwoodRule();
@Test
- public void testBatteryUsageStatsDataConsistency() {
+ public void testBatteryUsageStatsDataConsistency() throws Exception {
BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
BatteryUsageStats stats = bsm.getBatteryUsageStats(
new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
@@ -70,5 +70,7 @@ public class BatteryStatsManagerTest {
}
}
}
+
+ stats.close();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 2ccb6420bc43..69579d6cb7d8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -91,9 +91,14 @@ import java.util.function.IntConsumer;
public class BatteryStatsNoteTest {
@Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
- .setProvideMainThread(true)
- .build();
+ public final RavenwoodRule mRavenwood =
+ new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .setSystemPropertyImmutable(
+ "persist.sys.com.android.server.power.feature.flags."
+ + "framework_wakelock_info-override",
+ null)
+ .build();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -192,6 +197,7 @@ public class BatteryStatsNoteTest {
* Test BatteryStatsImpl.Uid.noteStartWakeLocked.
*/
@Test
+ @EnableFlags(com.android.server.power.feature.flags.Flags.FLAG_FRAMEWORK_WAKELOCK_INFO)
public void testNoteStartWakeLocked() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -222,6 +228,7 @@ public class BatteryStatsNoteTest {
* Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid.
*/
@Test
+ @EnableFlags(com.android.server.power.feature.flags.Flags.FLAG_FRAMEWORK_WAKELOCK_INFO)
public void testNoteStartWakeLocked_isolatedUid() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
@@ -264,6 +271,7 @@ public class BatteryStatsNoteTest {
* isolated uid is removed from batterystats before the wakelock has been stopped.
*/
@Test
+ @EnableFlags(com.android.server.power.feature.flags.Flags.FLAG_FRAMEWORK_WAKELOCK_INFO)
public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index 3931201aaf03..5912a9959e02 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -134,7 +134,7 @@ public class BatteryStatsUserLifecycleTests {
private int getNumberOfUidsInBatteryStats() throws Exception {
ArraySet<Integer> uids = new ArraySet<>();
- final String dumpsys = executeShellCommand("dumpsys batterystats --checkin");
+ final String dumpsys = executeShellCommand("dumpsys batterystats -c");
for (String line : dumpsys.split("\n")) {
final String[] parts = line.trim().split(",");
if (parts.length < 5 ||
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index c5157b373ee6..9da89fcf2e84 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -56,13 +57,14 @@ public class BatteryUsageStatsAtomTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ private static final boolean DEBUG = false;
private static final int UID_0 = 1000;
private static final int UID_1 = 2000;
private static final int UID_2 = 3000;
private static final int UID_3 = 4000;
@Test
- public void testAtom_BatteryUsageStatsPerUid() {
+ public void testAtom_BatteryUsageStatsPerUid() throws Exception {
final BatteryUsageStats bus = buildBatteryUsageStats();
BatteryStatsService.FrameworkStatsLogger statsLogger =
mock(BatteryStatsService.FrameworkStatsLogger.class);
@@ -70,6 +72,12 @@ public class BatteryUsageStatsAtomTest {
List<StatsEvent> actual = new ArrayList<>();
new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual);
+ bus.close();
+
+ if (DEBUG) {
+ System.out.println(mockingDetails(statsLogger).printInvocations());
+ }
+
// Device-wide totals
verify(statsLogger).buildStatsEvent(
1000L,
@@ -183,7 +191,7 @@ public class BatteryUsageStatsAtomTest {
"cpu",
1650.0f,
9300.0f,
- 8400L
+ 8300L
);
verify(statsLogger).buildStatsEvent(
1000L,
@@ -197,7 +205,7 @@ public class BatteryUsageStatsAtomTest {
"cpu",
1650.0f,
9400.0f,
- 0L
+ 8400L
);
verify(statsLogger).buildStatsEvent(
1000L,
@@ -278,7 +286,7 @@ public class BatteryUsageStatsAtomTest {
}
@Test
- public void testAtom_BatteryUsageStatsAtomsProto() {
+ public void testAtom_BatteryUsageStatsAtomsProto() throws Exception {
final BatteryUsageStats bus = buildBatteryUsageStats();
final byte[] bytes = bus.getStatsProto();
BatteryUsageStatsAtomsProto proto;
@@ -303,10 +311,6 @@ public class BatteryUsageStatsAtomTest {
bus.getAggregateBatteryConsumer(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE),
proto.deviceBatteryConsumer);
- for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) {
- assertPowerComponentModel(i, abc.getPowerModel(i), proto);
- }
-
// Now for the UidBatteryConsumers.
final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
uidConsumers.sort((a, b) -> a.getUid() - b.getUid());
@@ -341,6 +345,7 @@ public class BatteryUsageStatsAtomTest {
// UID_3 - Should be none, since no interesting data (done last for debugging convenience).
assertEquals(3, proto.uidBatteryConsumers.length);
+ bus.close();
}
private void assertSameBatteryConsumer(String message, BatteryConsumer consumer,
@@ -441,34 +446,6 @@ public class BatteryUsageStatsAtomTest {
}
}
- /**
- * Validates the PowerComponentModel object that matches powerComponent.
- */
- private void assertPowerComponentModel(int powerComponent,
- @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto) {
- boolean found = false;
- for (BatteryUsageStatsAtomsProto.PowerComponentModel powerComponentModel :
- proto.componentModels) {
- if (powerComponentModel.component == powerComponent) {
- if (found) {
- fail("Power component " + BatteryConsumer.powerComponentIdToString(
- powerComponent) + " found multiple times in the proto");
- }
- found = true;
- final int expectedPowerModel = BatteryConsumer.powerModelToProtoEnum(powerModel);
- assertEquals(expectedPowerModel, powerComponentModel.powerModel);
- }
- }
- if (!found) {
- final int model = BatteryConsumer.powerModelToProtoEnum(powerModel);
- assertEquals(
- "Power component " + BatteryConsumer.powerComponentIdToString(powerComponent)
- + " was not found in the proto but has a defined power model.",
- BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED,
- model);
- }
- }
-
/** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
private long convertMahToDc(double powerMah) {
return (long) (powerMah * 36 + 0.5);
@@ -477,7 +454,6 @@ public class BatteryUsageStatsAtomTest {
private BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
- /* includePowerModels */ true,
/* includeProcessStats */ true,
/* includeScreenStateData */ false,
/* includePowerStateData */ false,
@@ -493,17 +469,17 @@ public class BatteryUsageStatsAtomTest {
.setPackageWithHighestDrain("myPackage0")
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000)
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 2000)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 400)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 450)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 500)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 600)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 800);
final BatteryConsumer.Key keyFg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
@@ -515,14 +491,14 @@ public class BatteryUsageStatsAtomTest {
final BatteryConsumer.Key keyCached = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_CACHED);
- uidBuilder.setConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(keyFg, 8100)
- .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
- .setUsageDurationMillis(keyBg, 8200)
- .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
- .setUsageDurationMillis(keyFgs, 8300)
- .setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
- .setUsageDurationMillis(keyFgs, 8400);
+ uidBuilder.addConsumedPower(keyFg, 9100)
+ .addUsageDurationMillis(keyFg, 8100)
+ .addConsumedPower(keyBg, (double) 9200)
+ .addUsageDurationMillis(keyBg, 8200)
+ .addConsumedPower(keyFgs, (double) 9300)
+ .addUsageDurationMillis(keyFgs, 8300)
+ .addConsumedPower(keyCached, (double) 9400)
+ .addUsageDurationMillis(keyCached, 8400);
final BatteryConsumer.Key keyCustomFg = uidBuilder.getKey(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
@@ -530,55 +506,49 @@ public class BatteryUsageStatsAtomTest {
final BatteryConsumer.Key keyCustomBg = uidBuilder.getKey(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- uidBuilder.setConsumedPower(
- keyCustomFg, 100, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
- uidBuilder.setConsumedPower(
- keyCustomBg, 350, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ uidBuilder.addConsumedPower(keyCustomFg, 100);
+ uidBuilder.addConsumedPower(keyCustomBg, 350);
builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
.setPackageWithHighestDrain("myPackage1")
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234);
builder.getOrCreateUidBatteryConsumerBuilder(UID_2)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
766);
builder.getOrCreateUidBatteryConsumerBuilder(UID_3);
builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
- .setConsumedPower(30000)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 20100,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_AUDIO, 0,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
- BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
- .setConsumedPower(
+ .addConsumedPower(30000)
+ .addConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU, 20100)
+ .addConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_AUDIO, 0) // Empty
+ .addConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CAMERA, 20150)
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 20300)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400);
// Not used; just to make sure extraneous data doesn't mess things up.
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 10100,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setConsumedPower(
+ .addConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU, 10100)
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
return builder.build();
}
@Test
- public void testLargeAtomTruncated() {
+ public void testLargeAtomTruncated() throws Exception {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
+ new BatteryUsageStats.Builder(new String[0], false, false, false, 0);
// If not truncated, this BatteryUsageStats object would generate a proto buffer
// significantly larger than 50 Kb
for (int i = 0; i < 3000; i++) {
@@ -587,8 +557,8 @@ public class BatteryUsageStatsAtomTest {
BatteryConsumer.PROCESS_STATE_FOREGROUND, 1 * 60 * 1000)
.setTimeInProcessStateMs(
BatteryConsumer.PROCESS_STATE_BACKGROUND, 2 * 60 * 1000)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
}
// Add a UID with much larger battery footprint
@@ -596,22 +566,24 @@ public class BatteryUsageStatsAtomTest {
builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerUid)
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 10 * 60 * 1000)
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 20 * 60 * 1000)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
// Add a UID with much larger usage duration
final int highUsageUid = 3002;
builder.getOrCreateUidBatteryConsumerBuilder(highUsageUid)
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 60 * 60 * 1000)
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 120 * 60 * 1000)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
BatteryUsageStats batteryUsageStats = builder.build();
final byte[] bytes = batteryUsageStats.getStatsProto();
- assertThat(bytes.length).isGreaterThan(40000);
+ assertThat(bytes.length).isGreaterThan(20000);
assertThat(bytes.length).isLessThan(50000);
+ batteryUsageStats.close();
+
BatteryUsageStatsAtomsProto proto;
try {
proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index fde84e967c98..709f83ba907d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,7 +47,9 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.processor.MultiStatePowerAttributor;
import org.junit.Before;
import org.junit.Rule;
@@ -74,9 +77,11 @@ public class BatteryUsageStatsProviderTest {
new BatteryUsageStatsRule(12345)
.createTempDirectory()
.setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
- .setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+ .setAveragePower(PowerProfile.POWER_AUDIO, 720.0)
+ .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
private MockClock mMockClock = mStatsRule.getMockClock();
+ private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
private Context mContext;
@Before
@@ -91,15 +96,8 @@ public class BatteryUsageStatsProviderTest {
}
@Test
- public void test_getBatteryUsageStats() {
- BatteryStatsImpl batteryStats = prepareBatteryStats();
-
- BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
- mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
-
- final BatteryUsageStats batteryUsageStats =
- provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
+ public void test_getBatteryUsageStats() throws IOException {
+ final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
final List<UidBatteryConsumer> uidBatteryConsumers =
batteryUsageStats.getUidBatteryConsumers();
@@ -124,16 +122,34 @@ public class BatteryUsageStatsProviderTest {
.isWithin(PRECISION).of(0.4);
assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
- assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(54321);
+ assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(180 * MINUTE_IN_MS);
+ batteryUsageStats.close();
+ }
+
+ @Test
+ public void batteryLevelInfo_charging() throws IOException {
+ final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(true);
+ assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
+ assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(1_200_000);
+ batteryUsageStats.close();
}
@Test
- public void test_selectPowerComponents() {
- BatteryStatsImpl batteryStats = prepareBatteryStats();
+ public void batteryLevelInfo_onBattery() throws IOException {
+ final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
+ assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
+ assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(600_000);
+ batteryUsageStats.close();
+ }
+
+ @Test
+ public void test_selectPowerComponents() throws IOException {
+ BatteryStatsImpl batteryStats = prepareBatteryStats(false);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+ mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+ mMonotonicClock);
final BatteryUsageStats batteryUsageStats =
provider.getBatteryUsageStats(batteryStats,
@@ -153,10 +169,19 @@ public class BatteryUsageStatsProviderTest {
assertThat(
uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isEqualTo(0);
+
+ batteryUsageStats.close();
}
- private BatteryStatsImpl prepareBatteryStats() {
+ private BatteryStatsImpl prepareBatteryStats(boolean plugInAtTheEnd) {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.onSystemReady(mContext);
+
+ synchronized (batteryStats) {
+ batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 0,
+ 0, 0);
+ }
mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
synchronized (batteryStats) {
@@ -197,25 +222,68 @@ public class BatteryUsageStatsProviderTest {
ActivityManager.PROCESS_STATE_CACHED_EMPTY);
}
synchronized (batteryStats) {
- batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
+ batteryStats.noteFlashlightOnLocked(APP_UID, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
}
synchronized (batteryStats) {
- batteryStats.noteFlashlightOffLocked(APP_UID, 5000, 5000);
+ batteryStats.noteFlashlightOffLocked(APP_UID, 80 * MINUTE_IN_MS + 4000,
+ 80 * MINUTE_IN_MS + 4000);
}
synchronized (batteryStats) {
- batteryStats.noteAudioOnLocked(APP_UID, 10000, 10000);
+ batteryStats.noteAudioOnLocked(APP_UID, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
}
synchronized (batteryStats) {
- batteryStats.noteAudioOffLocked(APP_UID, 20000, 20000);
+ batteryStats.noteAudioOffLocked(APP_UID, 90 * MINUTE_IN_MS + 10000,
+ 90 * MINUTE_IN_MS + 10000);
}
- mStatsRule.setCurrentTime(54321);
+ synchronized (batteryStats) {
+ batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 60, 72, 3700, 2_600_000, 4_000_000, 0,
+ 100 * MINUTE_IN_MS, 100 * MINUTE_IN_MS, 21000);
+ }
+
+ synchronized (batteryStats) {
+ batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 30, 72, 3700, 1_600_000, 4_000_000, 0,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS, 21000);
+ }
+
+ if (plugInAtTheEnd) {
+ synchronized (batteryStats) {
+ batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_CHARGING,
+ 100, /* plugType */ BatteryManager.BATTERY_PLUGGED_USB, 30, 72, 3700,
+ 1_600_000, 4_000_000, /* chargeTimeToFullSeconds */ 20 * 60,
+ 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 22000);
+ }
+ }
+
+ setTime(180 * MINUTE_IN_MS);
+
return batteryStats;
}
+ private BatteryUsageStats prepareBatteryUsageStats(boolean plugInAtTheEnd) {
+ BatteryStatsImpl batteryStats = prepareBatteryStats(plugInAtTheEnd);
+
+ MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
+ mock(PowerStatsStore.class), mStatsRule.getPowerProfile(),
+ mStatsRule.getCpuScalingPolicies(), () -> 3500);
+ powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_AUDIO,
+ true);
+ powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
+ true);
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+ powerAttributor, mStatsRule.getPowerProfile(),
+ mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+ mMonotonicClock);
+
+ return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
+ }
+
@Test
- public void testWriteAndReadHistory() {
+ public void testWriteAndReadHistory() throws IOException {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
synchronized (batteryStats) {
batteryStats.setRecordAllHistoryLocked(true);
@@ -239,7 +307,8 @@ public class BatteryUsageStatsProviderTest {
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+ mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+ mMonotonicClock);
final BatteryUsageStats batteryUsageStats =
provider.getBatteryUsageStats(batteryStats,
@@ -247,6 +316,8 @@ public class BatteryUsageStatsProviderTest {
Parcel in = Parcel.obtain();
batteryUsageStats.writeToParcel(in, 0);
+ batteryUsageStats.close();
+
final byte[] bytes = in.marshall();
Parcel out = Parcel.obtain();
@@ -290,10 +361,12 @@ public class BatteryUsageStatsProviderTest {
assertThat(iterator.hasNext()).isFalse();
assertThat(iterator.next()).isNull();
+
+ unparceled.close();
}
@Test
- public void testWriteAndReadHistoryTags() {
+ public void testWriteAndReadHistoryTags() throws IOException {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
synchronized (batteryStats) {
batteryStats.setRecordAllHistoryLocked(true);
@@ -328,7 +401,8 @@ public class BatteryUsageStatsProviderTest {
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+ mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+ mMonotonicClock);
final BatteryUsageStats batteryUsageStats =
provider.getBatteryUsageStats(batteryStats,
@@ -341,6 +415,8 @@ public class BatteryUsageStatsProviderTest {
assertThat(parcel.dataSize()).isAtMost(128_000);
}
+ batteryUsageStats.close();
+
parcel.setDataPosition(0);
BatteryUsageStats unparceled = parcel.readParcelable(getClass().getClassLoader(),
@@ -402,7 +478,7 @@ public class BatteryUsageStatsProviderTest {
}
@Test
- public void testAggregateBatteryStats() {
+ public void testAggregateBatteryStats() throws IOException {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
setTime(5 * MINUTE_IN_MS);
@@ -417,9 +493,11 @@ public class BatteryUsageStatsProviderTest {
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
+ mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock,
+ mMonotonicClock);
- batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+ batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
+ /* accumulateBatteryUsageStats */ false);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -503,6 +581,144 @@ public class BatteryUsageStatsProviderTest {
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isWithin(0.1)
.of(180.0);
+
+ stats.close();
+ }
+
+ @Test
+ public void accumulateBatteryUsageStats() throws Throwable {
+ accumulateBatteryUsageStats(10000000, 1);
+ // Accumulate every 200 bytes of battery history
+ accumulateBatteryUsageStats(200, 2);
+ accumulateBatteryUsageStats(50, 5);
+ // Accumulate on every invocation of accumulateBatteryUsageStats
+ accumulateBatteryUsageStats(0, 7);
+ }
+
+ private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
+ int expectedNumberOfUpdates) throws Throwable {
+ BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats());
+ // Note - these two are in microseconds
+ when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L);
+ when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L);
+ batteryStats.forceRecordAllHistory();
+
+ setTime(5 * MINUTE_IN_MS);
+
+ // Capture the session start timestamp
+ synchronized (batteryStats) {
+ batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ PowerStatsStore powerStatsStore = spy(new PowerStatsStore(
+ new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
+ mStatsRule.getHandler()));
+ powerStatsStore.reset();
+
+ int[] count = new int[1];
+ doAnswer(inv -> {
+ count[0]++;
+ return null;
+ }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
+
+ MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
+ powerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
+ () -> 3500);
+ for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ powerComponentId++) {
+ powerAttributor.setPowerComponentSupported(powerComponentId, true);
+ }
+ powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+ powerAttributor, mStatsRule.getPowerProfile(),
+ mStatsRule.getCpuScalingPolicies(), powerStatsStore,
+ accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
+
+ provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOnLocked(APP_UID,
+ 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ }
+
+ provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOffLocked(APP_UID,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ }
+
+ provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOnLocked(APP_UID,
+ 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+ }
+
+ provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOffLocked(APP_UID,
+ 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
+ }
+ setTime(55 * MINUTE_IN_MS);
+
+ provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
+ // This section has not been saved yet, but should be added to the accumulated totals
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOnLocked(APP_UID,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ }
+
+ provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
+ synchronized (batteryStats) {
+ batteryStats.noteFlashlightOffLocked(APP_UID,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+ }
+ setTime(115 * MINUTE_IN_MS);
+
+ // Pick up the remainder of battery history that has not yet been accumulated
+ provider.accumulateBatteryUsageStats(batteryStats);
+
+ mStatsRule.waitForBackgroundThread();
+
+ BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
+ new BatteryUsageStatsQuery.Builder().accumulated().build());
+
+ assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
+ assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
+
+ assertThat(stats.getBatteryTimeRemainingMs()).isEqualTo(111);
+ assertThat(stats.getChargeTimeRemainingMs()).isEqualTo(777);
+ assertThat(stats.getBatteryCapacity()).isEqualTo(4000); // from PowerProfile
+
+ // Total: 10 + 20 + 30 = 60
+ assertThat(stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isWithin(0.0001)
+ .of(360.0); // 360 mA * 1.0 hour
+ assertThat(stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60 * MINUTE_IN_MS);
+
+ final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
+ .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
+ assertThat(uidBatteryConsumer
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isWithin(0.1)
+ .of(360.0);
+ assertThat(uidBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60 * MINUTE_IN_MS);
+
+ assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
+
+ stats.close();
}
private void setTime(long timeMs) {
@@ -528,7 +744,8 @@ public class BatteryUsageStatsProviderTest {
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+ mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+ mMonotonicClock);
PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
doAnswer(invocation -> {
@@ -547,10 +764,12 @@ public class BatteryUsageStatsProviderTest {
.isWithin(PRECISION).of(8.33333);
assertThat(uid.getConsumedPower(componentId1))
.isWithin(PRECISION).of(8.33333);
+ stats.close();
return null;
- }).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+ }).when(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
- mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+ mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore,
+ /* accumulateBatteryUsageStats */ false);
// Make an incompatible change of supported energy components. This will trigger
// a BatteryStats reset, which will generate a snapshot of battery stats.
@@ -559,11 +778,11 @@ public class BatteryUsageStatsProviderTest {
mStatsRule.waitForBackgroundThread();
- verify(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+ verify(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
}
@Test
- public void testAggregateBatteryStats_incompatibleSnapshot() {
+ public void testAggregateBatteryStats_incompatibleSnapshot() throws IOException {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
@@ -589,9 +808,12 @@ public class BatteryUsageStatsProviderTest {
when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
.thenReturn(span1);
+ span1.close();
+
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
+ mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock,
+ mMonotonicClock);
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
.aggregateSnapshots(0, 3000)
@@ -600,5 +822,7 @@ public class BatteryUsageStatsProviderTest {
assertThat(stats.getCustomPowerComponentNames())
.isEqualTo(batteryStats.getCustomEnergyConsumerNames());
assertThat(stats.getStatsDuration()).isEqualTo(1234);
+
+ stats.close();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 624b18948c49..a3c7ece386c7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -323,6 +323,7 @@ public class BatteryUsageStatsRule implements TestRule {
}
private void before() {
+ BatteryUsageStats.DEBUG_INSTANCE_COUNT = true;
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.setUncaughtExceptionHandler((thread, throwable)-> {
mThrowable = throwable;
@@ -338,6 +339,10 @@ public class BatteryUsageStatsRule implements TestRule {
private void after() throws Throwable {
waitForBackgroundThread();
+ if (mBatteryUsageStats != null) {
+ mBatteryUsageStats.close();
+ }
+ BatteryUsageStats.assertAllInstancesClosed();
}
public void waitForBackgroundThread() throws Throwable {
@@ -376,6 +381,23 @@ public class BatteryUsageStatsRule implements TestRule {
return mBatteryStats.getUidStatsLocked(uid);
}
+ /**
+ * Adds the supplied duration to all three: current time, elapsed time and uptime
+ */
+ public void advanceTime(long millis) {
+ mMockClock.currentTime += millis;
+ mMockClock.realtime += millis;
+ mMockClock.uptime += millis;
+ }
+
+ /**
+ * Adds the supplied duration to current time and elapsed time, but not to uptime
+ */
+ public void advanceSuspendedTime(long millis) {
+ mMockClock.currentTime += millis;
+ mMockClock.realtime += millis;
+ }
+
public void setTime(long realtimeMs, long uptimeMs) {
mMockClock.currentTime = realtimeMs;
mMockClock.realtime = realtimeMs;
@@ -392,9 +414,15 @@ public class BatteryUsageStatsRule implements TestRule {
}
BatteryUsageStats apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) {
+ if (mBatteryUsageStats != null) {
+ try {
+ mBatteryUsageStats.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ mBatteryUsageStats = null;
+ }
final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames();
- final boolean includePowerModels = (query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
final boolean includeScreenStateData = (query.getFlags()
@@ -403,7 +431,7 @@ public class BatteryUsageStatsRule implements TestRule {
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customPowerComponentNames, includePowerModels, includeProcessStateData,
+ customPowerComponentNames, includeProcessStateData,
includeScreenStateData, includePowerStateData, minConsumedPowerThreshold);
SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
for (int i = 0; i < uidStats.size(); i++) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 3ae4c3245941..dd50431b598e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -17,8 +17,6 @@
package com.android.server.power.stats;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
-import static android.os.BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION;
-import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED;
import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
@@ -76,22 +74,25 @@ public class BatteryUsageStatsTest {
private static final int APP_UID2 = 314;
@Test
- public void testBuilder() {
+ public void testBuilder() throws Exception {
BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(true).build();
assertBatteryUsageStats1(batteryUsageStats, true);
+ batteryUsageStats.close();
}
@Test
- public void testBuilder_noProcessStateData() {
+ public void testBuilder_noProcessStateData() throws Exception {
BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(false).build();
assertBatteryUsageStats1(batteryUsageStats, false);
+ batteryUsageStats.close();
}
@Test
- public void testParcelability_smallNumberOfUids() {
+ public void testParcelability_smallNumberOfUids() throws Exception {
final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats1(true).build();
final Parcel parcel = Parcel.obtain();
parcel.writeParcelable(outBatteryUsageStats, 0);
+ outBatteryUsageStats.close();
assertThat(parcel.dataSize()).isLessThan(100000);
@@ -101,10 +102,11 @@ public class BatteryUsageStatsTest {
parcel.readParcelable(getClass().getClassLoader());
assertThat(inBatteryUsageStats).isNotNull();
assertBatteryUsageStats1(inBatteryUsageStats, true);
+ inBatteryUsageStats.close();
}
@Test
- public void testParcelability_largeNumberOfUids() {
+ public void testParcelability_largeNumberOfUids() throws Exception {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[0]);
@@ -115,7 +117,7 @@ public class BatteryUsageStatsTest {
BatteryStatsImpl.Uid mockUid = mock(BatteryStatsImpl.Uid.class);
when(mockUid.getUid()).thenReturn(i);
builder.getOrCreateUidBatteryConsumerBuilder(mockUid)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, i * 100);
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, i * 100);
}
BatteryUsageStats outBatteryUsageStats = builder.build();
@@ -141,22 +143,27 @@ public class BatteryUsageStatsTest {
assertThat(uidBatteryConsumer).isNotNull();
assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(i * 100);
}
+ inBatteryUsageStats.close();
+ outBatteryUsageStats.close();
}
@Test
- public void testDefaultSessionDuration() {
+ public void testDefaultSessionDuration() throws Exception {
final BatteryUsageStats stats =
buildBatteryUsageStats1(true).setStatsDuration(10000).build();
assertThat(stats.getStatsDuration()).isEqualTo(10000);
+ stats.close();
}
@Test
- public void testDump() {
+ public void testDump() throws Exception {
final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
final StringWriter out = new StringWriter();
try (PrintWriter pw = new PrintWriter(out)) {
stats.dump(pw, " ");
}
+ stats.close();
+
final String dump = out.toString();
assertThat(dump).contains("Capacity: 4000");
@@ -173,7 +180,9 @@ public class BatteryUsageStatsTest {
assertThat(dump).containsMatch(quote("(not on battery, screen off/doze)") + "\\s*"
+ "cpu: 123 apps: 123 duration: 456ms");
assertThat(dump).containsMatch(
- "UID 271: 1200 fg: 1777 bg: 2388 fgs: 1999 cached: 123\\s*"
+ quote("UID 271: 1200 "
+ + "fg: 1777 (1s 0ms) bg: 2388 (1s 500ms) fgs: 1999 (500ms) cached: 123")
+ + "\\s*"
+ quote("screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) "
+ "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
+ "cpu:cached=123 (456ms) FOO=500 (800ms) FOO:bg=500 (800ms)") + "\\s*"
@@ -185,12 +194,14 @@ public class BatteryUsageStatsTest {
}
@Test
- public void testDumpNoScreenOrPowerState() {
+ public void testDumpNoScreenOrPowerState() throws Exception {
final BatteryUsageStats stats = buildBatteryUsageStats1(true, false, false).build();
final StringWriter out = new StringWriter();
try (PrintWriter pw = new PrintWriter(out)) {
stats.dump(pw, " ");
}
+ stats.close();
+
final String dump = out.toString();
assertThat(dump).contains("Capacity: 4000");
@@ -199,7 +210,9 @@ public class BatteryUsageStatsTest {
assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
assertThat(dump).containsMatch(
- "UID 271: 1200 fg: 1777 bg: 2388 fgs: 1999 cached: 123\\s*"
+ quote("UID 271: 1200 "
+ + "fg: 1777 (1s 0ms) bg: 2388 (1s 500ms) fgs: 1999 (500ms) cached: 123")
+ + "\\s*"
+ quote("screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) "
+ "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
+ "cpu:cached=123 (456ms) FOO=500 (800ms) FOO:bg=500 (800ms)"));
@@ -218,11 +231,11 @@ public class BatteryUsageStatsTest {
}
@Test
- public void testAdd() {
+ public void testAdd() throws Exception {
final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
final BatteryUsageStats sum =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0)
.add(stats1)
.add(stats2)
.build();
@@ -233,15 +246,13 @@ public class BatteryUsageStatsTest {
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200 + 924, null,
- 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400 + 345,
- POWER_MODEL_UNDEFINED,
+ 5321, 6900, 532, 423, 400 + 345,
500 + 456, 1167, 1478,
true, 3554, 4732, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
444, 1110);
} else if (uidBatteryConsumer.getUid() == APP_UID2) {
assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
- 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+ 1111, 2220, 2, 333, 444,
555, 666, 777,
true, 1777, 2443, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
321, 654);
@@ -257,24 +268,31 @@ public class BatteryUsageStatsTest {
assertAggregateBatteryConsumer(sum,
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
40211, 40422, 40633, 40844);
+ stats1.close();
+ stats2.close();
+ sum.close();
}
@Test
- public void testAdd_customComponentMismatch() {
+ public void testAdd_customComponentMismatch() throws Exception {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+ stats.close();
+ builder.discard();
}
@Test
- public void testAdd_processStateDataMismatch() {
+ public void testAdd_processStateDataMismatch() throws Exception {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
+ stats.close();
+ builder.discard();
}
@Test
@@ -286,12 +304,14 @@ public class BatteryUsageStatsTest {
final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
stats.writeXml(serializer);
serializer.endDocument();
+ stats.close();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
TypedXmlPullParser parser = Xml.newBinaryPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
assertBatteryUsageStats1(fromXml, true);
+ fromXml.close();
}
private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) {
@@ -304,7 +324,7 @@ public class BatteryUsageStatsTest {
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true,
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true,
includeScreenState, includePowerState, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
@@ -315,8 +335,8 @@ public class BatteryUsageStatsTest {
addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo",
1000, 1500, 500,
- 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800,
+ 300, 400,
+ 500, 600, 800,
1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
addAggregateBatteryConsumer(builder,
@@ -331,13 +351,13 @@ public class BatteryUsageStatsTest {
if (includeUserBatteryConsumer) {
builder.getOrCreateUserBatteryConsumerBuilder(USER_ID)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 10)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 30)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40);
}
return builder;
@@ -349,7 +369,7 @@ public class BatteryUsageStatsTest {
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(customPowerComponentNames, true,
+ new BatteryUsageStats.Builder(customPowerComponentNames,
includeProcessStateData, true, true, 0);
builder.setDischargePercentage(30)
.setDischargedPowerRange(1234, 2345)
@@ -358,14 +378,14 @@ public class BatteryUsageStatsTest {
addUidBatteryConsumer(builder, batteryStats, APP_UID1, null,
4321, 5400, 32,
- 123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_ENERGY_CONSUMPTION,
+ 123, 345,
456, 567, 678,
1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar",
1111, 2220, 2,
- 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777,
+ 333, 444,
+ 555, 666, 777,
1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
addAggregateBatteryConsumer(builder,
@@ -385,7 +405,7 @@ public class BatteryUsageStatsTest {
MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain,
int timeInProcessStateForeground, int timeInProcessStateBackground,
int timeInProcessStateForegroundService, double screenPower,
- int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower,
+ double cpuPower, double customComponentPower,
int cpuDuration, int customComponentDuration, double cpuPowerForeground,
int cpuDurationForeground, double cpuPowerBackground, int cpuDurationBackground,
double cpuPowerFgs, int cpuDurationFgs, double cpuPowerCached, long cpuDurationCached) {
@@ -398,15 +418,15 @@ public class BatteryUsageStatsTest {
.setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, timeInProcessStateBackground)
.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
timeInProcessStateForegroundService)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel)
- .setConsumedPower(
+ .addConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower)
+ .addConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
if (builder.isProcessStateDataNeeded()) {
final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded()
@@ -436,22 +456,16 @@ public class BatteryUsageStatsTest {
: uidBuilder.getKey(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- uidBuilder
- .setConsumedPower(cpuFgKey, cpuPowerForeground,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuFgKey, cpuDurationForeground)
- .setConsumedPower(cpuBgKey, cpuPowerBackground,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuBgKey, cpuDurationBackground)
- .setConsumedPower(cpuFgsKey, cpuPowerFgs,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
- .setConsumedPower(cachedKey, cpuPowerCached,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cachedKey, cpuDurationCached)
- .setConsumedPower(customBgKey, customComponentPower,
- BatteryConsumer.POWER_MODEL_UNDEFINED)
- .setUsageDurationMillis(customBgKey, customComponentDuration);
+ uidBuilder.addConsumedPower(cpuFgKey, cpuPowerForeground)
+ .addUsageDurationMillis(cpuFgKey, cpuDurationForeground)
+ .addConsumedPower(cpuBgKey, cpuPowerBackground)
+ .addUsageDurationMillis(cpuBgKey, cpuDurationBackground)
+ .addConsumedPower(cpuFgsKey, cpuPowerFgs)
+ .addUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
+ .addConsumedPower(cachedKey, cpuPowerCached)
+ .addUsageDurationMillis(cachedKey, cpuDurationCached)
+ .addConsumedPower(customBgKey, customComponentPower)
+ .addUsageDurationMillis(customBgKey, customComponentDuration);
}
}
@@ -462,15 +476,15 @@ public class BatteryUsageStatsTest {
long cpuDurationChgScrOn, double cpuPowerChgScrOff, long cpuDurationChgScrOff) {
final AggregateBatteryConsumer.Builder aggBuilder =
builder.getAggregateBatteryConsumerBuilder(scope)
- .setConsumedPower(consumedPower)
- .setConsumedPower(
+ .addConsumedPower(consumedPower)
+ .addConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
- .setConsumedPower(
+ .addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
customComponentPower)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
- .setUsageDurationMillis(
+ .addUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
customComponentDuration);
if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) {
@@ -494,19 +508,14 @@ public class BatteryUsageStatsTest {
BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
BatteryConsumer.SCREEN_STATE_OTHER,
BatteryConsumer.POWER_STATE_OTHER);
- aggBuilder
- .setConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
- .setConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
- .setConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
- .setConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
+ aggBuilder.addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn)
+ .addUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
+ .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff)
+ .addUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
+ .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn)
+ .addUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
+ .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff)
+ .addUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
}
}
@@ -520,8 +529,7 @@ public class BatteryUsageStatsTest {
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
- 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+ 1000, 1500, 500, 300, 400,
500, 600, 800,
true, 1777, 2388, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
} else {
@@ -572,8 +580,8 @@ public class BatteryUsageStatsTest {
private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer,
double consumedPower, String packageWithHighestDrain, int timeInProcessStateForeground,
int timeInProcessStateBackground, int timeInProcessStateForegroundService,
- int screenPower, int screenPowerModel, double cpuPower,
- int cpuPowerModel, double customComponentPower, int cpuDuration,
+ int screenPower, double cpuPower,
+ double customComponentPower, int cpuDuration,
int customComponentDuration, boolean processStateDataIncluded,
double totalPowerForeground, double totalPowerBackground, double totalPowerFgs,
double totalPowerCached, double cpuPowerForeground, int cpuDurationForeground,
@@ -596,12 +604,8 @@ public class BatteryUsageStatsTest {
PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(timeInProcessStateForegroundService);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower);
- assertThat(uidBatteryConsumer.getPowerModel(
- BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPowerModel);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower);
- assertThat(uidBatteryConsumer.getPowerModel(
- BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPowerModel);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower);
assertThat(uidBatteryConsumer.getUsageDurationMillis(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
index fe6424f91d83..c9cb0dfa98e4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
@@ -82,16 +82,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.06944, 3000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.06944, 3000);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.19444, 9000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.19444, 9000);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26388, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26388, 12000);
}
@Test
@@ -144,8 +144,6 @@ public class BluetoothPowerCalculatorTest {
.isEqualTo(6166);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
.isWithin(PRECISION).of(0.1226666);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
@@ -178,16 +176,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.08216, 3583);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.18169, 8416);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.30030, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.30030, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26386, 11999);
}
@Test
@@ -202,16 +200,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.10378, 3583, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.10378, 3583);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.22950, 8416, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.22950, 8416);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.33333, 12000, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.33333, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.33329, 11999, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.33329, 11999);
}
@Test
@@ -264,8 +262,6 @@ public class BluetoothPowerCalculatorTest {
.isEqualTo(6166);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
.isWithin(PRECISION).of(0.8220561);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
@@ -299,16 +295,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.08216, 3583);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.18169, 8416);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26388, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26386, 11999);
}
private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) {
@@ -326,14 +322,12 @@ public class BluetoothPowerCalculatorTest {
}
private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer,
- double powerMah, int durationMs, @BatteryConsumer.PowerModel int powerModel) {
+ double powerMah, int durationMs) {
assertThat(batteryConsumer).isNotNull();
double consumedPower = batteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
assertThat(consumedPower).isWithin(PRECISION).of(powerMah);
- assertThat(batteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
- .isEqualTo(powerModel);
long usageDurationMillis = batteryConsumer.getUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
index 7225f2d3995c..4cd3857a80bb 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
@@ -70,16 +70,12 @@ public class CameraPowerCalculatorTest {
.isEqualTo(1000);
assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.1);
- assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isEqualTo(2000);
assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.2);
- assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer
@@ -87,8 +83,6 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.3);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsBatteryConsumer
@@ -96,8 +90,6 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.3);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -122,16 +114,12 @@ public class CameraPowerCalculatorTest {
.isEqualTo(1000);
assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.2);
- assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isEqualTo(2000);
assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.3);
- assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer
@@ -139,8 +127,6 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.5);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsBatteryConsumer
@@ -148,7 +134,5 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.5);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 71a65c85d9e1..527db67a84ae 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -192,8 +192,6 @@ public class CpuPowerCalculatorTest {
.isEqualTo(3333);
assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(1.031677);
- assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -201,21 +199,15 @@ public class CpuPowerCalculatorTest {
.isEqualTo(7777);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(2.489544);
- assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(3.52122);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(3.52122);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -264,8 +256,6 @@ public class CpuPowerCalculatorTest {
.isEqualTo(3333);
assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(3.18877);
- assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -273,21 +263,15 @@ public class CpuPowerCalculatorTest {
.isEqualTo(7777);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(7.44072);
- assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(10.62949);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(10.62949);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -394,10 +378,7 @@ public class CpuPowerCalculatorTest {
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
+ counter.updateValues(cpuTimes, timestampMs);
return null;
}).when(mMockKernelSingleUidTimeReader).addDelta(eq(uid),
any(LongArrayMultiStateCounter.class), anyLong());
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
index 3b5658c2e40a..506bab4b3600 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
@@ -68,20 +68,14 @@ public class GnssPowerCalculatorTest {
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -107,27 +101,19 @@ public class GnssPowerCalculatorTest {
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(2.77777);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_GNSS))
.isEqualTo(2000);
assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(5.55555);
- assertThat(consumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(8.333333);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(8.333333);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 9b810bc01b4d..eba820e95783 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -164,8 +164,6 @@ public class MobileRadioPowerCalculatorTest {
// = 4604000 mA-ms or 1.27888 mA-h
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.27888);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
// + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
@@ -178,22 +176,16 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.94);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.705);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.235);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -321,8 +313,6 @@ public class MobileRadioPowerCalculatorTest {
// = 5177753 mA-ms or 1.43826 mA-h total consumption
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.43826);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
// + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
@@ -335,22 +325,16 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.09938);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.82453);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.27484);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -441,8 +425,6 @@ public class MobileRadioPowerCalculatorTest {
// = 4604000 mA-ms or 1.27888 mA-h
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.27888);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
// + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
@@ -455,22 +437,16 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.94);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.705);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.235);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -648,24 +624,16 @@ public class MobileRadioPowerCalculatorTest {
// 200ms phone on duration / 2000 total duration * 2.77778 mAh = 0.27777
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(2.5);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
.isWithin(PRECISION).of(0.27778);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.38541);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.38541);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -782,12 +750,8 @@ public class MobileRadioPowerCalculatorTest {
// 1000ms phone on duration / 10000 total duration * 2.77778 mAh = 0.27777
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(2.5);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
.isWithin(PRECISION).of(0.27778);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
// CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
// [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
@@ -817,8 +781,6 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.91094);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
// 240 ms Rx Time, 1110 ms Tx Time, 1350 ms active time
// 150 App 1 Rx Packets, 10 App 1 Tx packets
@@ -841,15 +803,11 @@ public class MobileRadioPowerCalculatorTest {
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.27574);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.63520);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -936,12 +894,8 @@ public class MobileRadioPowerCalculatorTest {
// 1000ms phone on duration / 10000 total duration * 2.77778 mAh = 0.27777
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(2.5);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
.isWithin(PRECISION).of(0.27778);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
// Estimated Rx/Tx modem consumption = 0.94 mAh
@@ -949,14 +903,10 @@ public class MobileRadioPowerCalculatorTest {
// 2.5 * 0.94 / 1.27888 = 1.83754 mAh
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.83754);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.83754);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 2c03f9d1a9aa..b374a3202fa2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -17,6 +17,7 @@
package com.android.server.power.stats;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.usage.NetworkStatsManager;
@@ -43,7 +44,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Queue;
-import java.util.concurrent.Future;
/**
* Mocks a BatteryStatsImpl object.
@@ -81,7 +81,7 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
mock(PlatformIdleStateCallback.class), mock(EnergyStatsRetriever.class),
- mock(UserInfoProvider.class), mock(PowerProfile.class),
+ mock(UserInfoProvider.class), mockPowerProfile(),
new CpuScalingPolicies(new SparseArray<>(), new SparseArray<>()),
powerStatsUidResolver, mock(FrameworkStatsLogger.class),
mock(BatteryStatsHistory.TraceDelegate.class),
@@ -97,6 +97,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
mKernelWakelockReader = null;
}
+ private static PowerProfile mockPowerProfile() {
+ PowerProfile powerProfile = mock(PowerProfile.class);
+ when(powerProfile.getNumDisplays()).thenReturn(1);
+ return powerProfile;
+ }
+
public void initMeasuredEnergyStats(String[] customBucketNames) {
final boolean[] supportedStandardBuckets =
new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
@@ -288,30 +294,25 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
public int flags = 0;
@Override
- public Future<?> scheduleSync(String reason, int flags) {
- return null;
+ public void scheduleSync(String reason, int flags) {
}
@Override
- public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
- return null;
+ public void scheduleCleanupDueToRemovedUser(int userId) {
}
@Override
- public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
- return null;
+ public void scheduleCpuSyncDueToRemovedUid(int uid) {
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
+ public void scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
flags |= flag;
- return null;
}
@Override
- public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
- return null;
+ public void scheduleCpuSyncDueToWakelockChange(long delayMillis) {
}
@Override
@@ -319,8 +320,7 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
}
@Override
- public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
- return null;
+ public void scheduleSyncDueToBatteryLevelChange(long delayMillis) {
}
@Override
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
index 2da98e8b9a61..7f20035253f0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
@@ -104,8 +104,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 200000000 + 5 / 45 * 300000000 mAs = 64.81 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(64.81481);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -116,8 +114,6 @@ public class ScreenPowerCalculatorTest {
// Uid2 charge = 40 / 45 * 300000000 + 100000000 mAs = 101.85 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(101.85185);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -126,8 +122,6 @@ public class ScreenPowerCalculatorTest {
// 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(166.66666);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -135,11 +129,8 @@ public class ScreenPowerCalculatorTest {
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(166.66666);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
-
@Test
public void testMeasuredEnergyBasedModel_multiDisplay() {
mStatsRule.initMeasuredEnergyStatsLocked()
@@ -202,8 +193,6 @@ public class ScreenPowerCalculatorTest {
// (600000000 + 800000000) uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(388.88888);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -214,8 +203,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 20 / 80 * 600000000 mAs = 41.66666 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(41.66666);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -226,17 +213,12 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 60 / 80 * 600000000 + 800000000 mAs = 347.22222 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(347.22222);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(110 * MINUTE_IN_MS);
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(388.88888);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
-
}
@Test
@@ -277,8 +259,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 20 / 80 * 92.0 = 23.0 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(23.0);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -288,27 +268,20 @@ public class ScreenPowerCalculatorTest {
// Uid2 charge = 60 / 80 * 92.0 = 69.0 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(69.0);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(80 * MINUTE_IN_MS);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(92);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(80 * MINUTE_IN_MS);
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(92);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
-
@Test
public void testPowerProfileBasedModel_multiDisplay() {
mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
@@ -364,8 +337,6 @@ public class ScreenPowerCalculatorTest {
// 92 + 60 * 0.5 + 10 * 0.1 + 90 * 0.2 + 30 * 0.2 = 147
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(147);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -375,8 +346,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 20 / 110 * 147.0 = 23.0 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(26.72727);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -386,17 +355,12 @@ public class ScreenPowerCalculatorTest {
// Uid2 charge = 90 / 110 * 92.0 = 69.0 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(120.272727);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(110 * MINUTE_IN_MS);
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(147);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
}
private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
index 5b7762d7de65..9645e9088bbb 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
@@ -23,12 +23,15 @@ import android.os.BatteryStats;
import android.os.Process;
import android.os.UidBatteryConsumer;
import android.os.WorkSource;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.PowerProfile;
+import com.android.server.power.feature.flags.Flags;
import org.junit.Rule;
import org.junit.Test;
@@ -38,20 +41,29 @@ import org.junit.runner.RunWith;
@SmallTest
public class WakelockPowerCalculatorTest {
@Rule(order = 0)
- public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
- .setProvideMainThread(true)
- .build();
+ public final RavenwoodRule mRavenwood =
+ new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .setSystemPropertyImmutable(
+ "persist.sys.com.android.server.power.feature.flags."
+ + "framework_wakelock_info-override",
+ null)
+ .build();
+
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final double PRECISION = 0.00001;
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
private static final int APP_PID = 3145;
- @Rule(order = 1)
- public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0);
+ @Rule(order = 2)
+ public final BatteryUsageStatsRule mStatsRule =
+ new BatteryUsageStatsRule().setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0);
@Test
+ @EnableFlags(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO)
public void testTimerBasedModel() {
mStatsRule.getUidStats(Process.ROOT_UID);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
new file mode 100644
index 000000000000..ed927c6ab699
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static android.os.BatteryConsumer.POWER_COMPONENT_WAKELOCK;
+import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.Process;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.PowerStats;
+import com.android.server.power.feature.flags.Flags;
+import com.android.server.power.stats.format.WakelockPowerStatsLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class WakelockPowerStatsCollectorTest {
+
+ @Rule
+ public final RavenwoodRule mRule =
+ new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(
+ "persist.sys.com.android.server.power.feature.flags."
+ + "framework_wakelock_info-override",
+ null)
+ .build();
+
+ @Rule(order = 0)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+
+ private MockBatteryStatsImpl mBatteryStats;
+
+ private final MockClock mClock = mStatsRule.getMockClock();
+ private PowerStats mPowerStats;
+ private WakelockPowerStatsLayout mStatsLayout = new WakelockPowerStatsLayout();
+
+ @Before
+ public void setup() throws Throwable {
+ mBatteryStats = mStatsRule.getBatteryStats();
+ mBatteryStats.setPowerStatsCollectorEnabled(POWER_COMPONENT_WAKELOCK, true);
+ mBatteryStats.getPowerStatsCollector(POWER_COMPONENT_WAKELOCK)
+ .addConsumer(ps -> mPowerStats = ps);
+ mBatteryStats.onSystemReady(mock(Context.class));
+ // onSystemReady schedules the initial power stats collection. Wait for it to finish
+ mStatsRule.waitForBackgroundThread();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO)
+ public void collectStats() {
+ PowerStatsCollector powerStatsCollector = mBatteryStats.getPowerStatsCollector(
+ POWER_COMPONENT_WAKELOCK);
+
+ mBatteryStats.forceRecordAllHistory();
+
+ mStatsRule.advanceSuspendedTime(1000);
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true, 0, 90, 1000);
+ }
+
+ mStatsRule.advanceSuspendedTime(3000);
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteStartWakeLocked(APP_UID1, 0, null, "one", null, WAKE_TYPE_PARTIAL,
+ false);
+ }
+
+ mStatsRule.advanceTime(1000);
+ powerStatsCollector.collectAndDeliverStats();
+
+ assertThat(mStatsLayout.getUsageDuration(mPowerStats.stats)).isEqualTo(1000);
+ assertThat(mStatsLayout.getUidUsageDuration(mPowerStats.uidStats.get(APP_UID1)))
+ .isEqualTo(1000);
+
+ mStatsRule.advanceTime(3000);
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteStartWakeLocked(APP_UID2, 0, null, "two", null, WAKE_TYPE_PARTIAL,
+ false);
+ }
+
+ mStatsRule.advanceTime(2000);
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteStopWakeLocked(APP_UID1, 0, null, "one", null, WAKE_TYPE_PARTIAL);
+ }
+
+ mStatsRule.advanceTime(5000);
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteStopWakeLocked(APP_UID2, 0, null, "two", null, WAKE_TYPE_PARTIAL);
+ }
+ mStatsRule.advanceSuspendedTime(7000);
+
+ // Plug in
+ synchronized (mBatteryStats) {
+ mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, false, 0, 90, 1000);
+ }
+
+ mStatsRule.advanceSuspendedTime(1000);
+ powerStatsCollector.collectAndDeliverStats();
+
+ // Based on the uptime, the device was awake for (3000+2000+5000) = 10000 ms
+ assertThat(mStatsLayout.getUsageDuration(mPowerStats.stats)).isEqualTo(10000);
+ assertThat(mStatsLayout.getUidUsageDuration(mPowerStats.uidStats.get(APP_UID1)))
+ .isEqualTo(4000); // 3000 + (2000/2) -- the 2000 ms overlap is split two-way
+ assertThat(mStatsLayout.getUidUsageDuration(mPowerStats.uidStats.get(APP_UID2)))
+ .isEqualTo(6000); // (2000/2) + 5000
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
new file mode 100644
index 000000000000..1fe3f58a9dcb
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.nano.OsProtoEnums;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WakelockStatsFrameworkEventsTest {
+ private WakelockStatsFrameworkEvents mEvents;
+
+ @Before
+ public void setup() {
+ mEvents = new WakelockStatsFrameworkEvents();
+ }
+
+ private static final int UID_1 = 1;
+ private static final int UID_2 = 2;
+
+ private static final String TAG_1 = "TAG1";
+ private static final String TAG_2 = "TAG2";
+
+ private static final int WAKELOCK_TYPE_1 = OsProtoEnums.PARTIAL_WAKE_LOCK;
+ private static final int WAKELOCK_TYPE_2 = OsProtoEnums.DOZE_WAKE_LOCK;
+
+ private static final long TS_1 = 1000;
+ private static final long TS_2 = 2000;
+ private static final long TS_3 = 3000;
+ private static final long TS_4 = 4000;
+ private static final long TS_5 = 5000;
+
+ // Mirrors com.android.os.framework.FrameworkWakelockInfo proto.
+ private static class WakelockInfo {
+ public int uid;
+ public String tag;
+ public int type;
+ public long uptimeMillis;
+ public long completedCount;
+
+ WakelockInfo(int uid, String tag, int type, long uptimeMillis, long completedCount) {
+ this.uid = uid;
+ this.tag = tag;
+ this.type = type;
+ this.uptimeMillis = uptimeMillis;
+ this.completedCount = completedCount;
+ }
+ }
+
+ // Assumes that mEvents is empty.
+ @SuppressWarnings("GuardedBy")
+ private void makeMetricsAlmostOverflow() throws Exception {
+ for (int i = 0; i < mEvents.SUMMARY_THRESHOLD - 1; i++) {
+ String tag = "forceOverflow" + i;
+ mEvents.noteStartWakeLock(UID_1, tag, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStopWakeLock(UID_1, tag, WAKELOCK_TYPE_1, TS_2);
+ }
+
+ assertFalse("not overflow", mEvents.inOverflow());
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+ WakelockInfo notOverflowInfo =
+ info.stream()
+ .filter(i -> i.tag.equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("not overflow", notOverflowInfo, null);
+
+ // Add one more to hit an overflow state.
+ String lastTag = "forceOverflowLast";
+ mEvents.noteStartWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2, TS_1);
+ mEvents.noteStopWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2, TS_2);
+
+ assertTrue("overflow", mEvents.inOverflow());
+ info = pullResults(TS_4);
+
+ WakelockInfo tag1Info =
+ info.stream().filter(i -> i.tag.equals(lastTag)).findFirst().orElse(null);
+
+ assertTrue("lastTag found", tag1Info != null);
+ assertEquals("uid", UID_1, tag1Info.uid);
+ assertEquals("tag", lastTag, tag1Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_2, tag1Info.type);
+ assertEquals("duration", TS_2 - TS_1, tag1Info.uptimeMillis);
+ assertEquals("count", 1, tag1Info.completedCount);
+ }
+
+ // Assumes that mEvents is empty.
+ @SuppressWarnings("GuardedBy")
+ private void makeMetricsAlmostHardCap() throws Exception {
+ for (int i = 0; i < mEvents.MAX_WAKELOCK_DIMENSIONS - 1; i++) {
+ mEvents.noteStartWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStopWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1, TS_2);
+ }
+
+ assertFalse("not hard capped", mEvents.inHardCap());
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+ WakelockInfo notOverflowInfo =
+ info.stream()
+ .filter(i -> i.tag.equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("not overflow", notOverflowInfo, null);
+
+ // Add one more to hit an hardcap state.
+ int hardCapUid = mEvents.MAX_WAKELOCK_DIMENSIONS;
+ mEvents.noteStartWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2, TS_1);
+ mEvents.noteStopWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2, TS_2);
+
+ assertTrue("hard capped", mEvents.inHardCap());
+ info = pullResults(TS_4);
+
+ WakelockInfo tag2Info =
+ info.stream().filter(i -> i.uid == hardCapUid).findFirst().orElse(null);
+
+ assertTrue("hardCapUid found", tag2Info != null);
+ assertEquals("uid", hardCapUid, tag2Info.uid);
+ assertEquals("tag", mEvents.OVERFLOW_TAG, tag2Info.tag);
+ assertEquals("type", mEvents.OVERFLOW_LEVEL, tag2Info.type);
+ assertEquals("duration", TS_2 - TS_1, tag2Info.uptimeMillis);
+ assertEquals("count", 1, tag2Info.completedCount);
+ }
+
+ private ArrayList<WakelockInfo> pullResults(long timestamp) {
+ ArrayList<WakelockInfo> results = new ArrayList<>();
+ WakelockStatsFrameworkEvents.EventLogger logger =
+ new WakelockStatsFrameworkEvents.EventLogger() {
+ public void logResult(
+ int uid,
+ String tag,
+ int wakeLockLevel,
+ long uptimeMillis,
+ long completedCount) {
+ WakelockInfo info =
+ new WakelockInfo(
+ uid, tag, wakeLockLevel, uptimeMillis, completedCount);
+ results.add(info);
+ }
+ };
+ mEvents.pullFrameworkWakelockInfoAtoms(timestamp, logger);
+ return results;
+ }
+
+ @Test
+ public void singleWakelock() {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_2);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).uid);
+ assertEquals("tag", TAG_1, info.get(0).tag);
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
+ assertEquals("duration", TS_2 - TS_1, info.get(0).uptimeMillis);
+ assertEquals("count", 1, info.get(0).completedCount);
+ }
+
+ @Test
+ public void wakelockOpen() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).uid);
+ assertEquals("tag", TAG_1, info.get(0).tag);
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
+ assertEquals("duration", TS_3 - TS_1, info.get(0).uptimeMillis);
+ assertEquals("count", 0, info.get(0).completedCount);
+ }
+
+ @Test
+ public void wakelockOpenOverlap() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_3);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).uid);
+ assertEquals("tag", TAG_1, info.get(0).tag);
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
+ assertEquals("duration", TS_4 - TS_1, info.get(0).uptimeMillis);
+ assertEquals("count", 0, info.get(0).completedCount);
+ }
+
+ @Test
+ public void testOverflow() throws Exception {
+ makeMetricsAlmostOverflow();
+
+ // This one gets tagged as an overflow.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_2);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+ WakelockInfo overflowInfo =
+ info.stream()
+ .filter(i -> i.tag.equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", UID_1, overflowInfo.uid);
+ assertEquals("type", mEvents.OVERFLOW_LEVEL, overflowInfo.type);
+ assertEquals("duration", TS_2 - TS_1, overflowInfo.uptimeMillis);
+ assertEquals("count", 1, overflowInfo.completedCount);
+ }
+
+ @Test
+ public void testOverflowOpen() throws Exception {
+ makeMetricsAlmostOverflow();
+
+ // This is the open wakelock that overflows.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+ WakelockInfo overflowInfo =
+ info.stream()
+ .filter(i -> i.tag.equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", UID_1, overflowInfo.uid);
+ assertEquals("type", mEvents.OVERFLOW_LEVEL, overflowInfo.type);
+ assertEquals("duration", (TS_4 - TS_1), overflowInfo.uptimeMillis);
+ assertEquals("count", 0, overflowInfo.completedCount);
+ }
+
+ @Test
+ public void testHardCap() throws Exception {
+ makeMetricsAlmostHardCap();
+
+ // This one gets tagged as a hard cap.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_2);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+ WakelockInfo hardCapInfo =
+ info.stream()
+ .filter(i -> i.tag.equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.uid);
+ assertEquals("type", mEvents.OVERFLOW_LEVEL, hardCapInfo.type);
+ assertEquals("duration", TS_2 - TS_1, hardCapInfo.uptimeMillis);
+ assertEquals("count", 1, hardCapInfo.completedCount);
+ }
+
+ @Test
+ public void testHardCapOpen() throws Exception {
+ makeMetricsAlmostHardCap();
+
+ // This is the open wakelock that overflows.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_4);
+ WakelockInfo hardCapInfo =
+ info.stream()
+ .filter(i -> i.tag.equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.uid);
+ assertEquals("type", mEvents.OVERFLOW_LEVEL, hardCapInfo.type);
+ assertEquals("duration", (TS_4 - TS_1), hardCapInfo.uptimeMillis);
+ assertEquals("count", 0, hardCapInfo.completedCount);
+ }
+
+ @Test
+ public void overlappingWakelocks() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_5);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).uid);
+ assertEquals("tag", TAG_1, info.get(0).tag);
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
+ assertEquals("duration", TS_4 - TS_1, info.get(0).uptimeMillis);
+ assertEquals("count", 1, info.get(0).completedCount);
+ }
+
+ @Test
+ public void diffUid() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStartWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1, TS_2);
+ mEvents.noteStopWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1, TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ WakelockInfo uid1Info = info.stream().filter(i -> i.uid == UID_1).findFirst().orElse(null);
+
+ assertTrue("UID_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.uid);
+ assertEquals("tag", TAG_1, uid1Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.type);
+ assertEquals("duration", TS_4 - TS_1, uid1Info.uptimeMillis);
+ assertEquals("count", 1, uid1Info.completedCount);
+
+ WakelockInfo uid2Info = info.stream().filter(i -> i.uid == UID_2).findFirst().orElse(null);
+ assertTrue("UID_2 found", uid2Info != null);
+ assertEquals("uid", UID_2, uid2Info.uid);
+ assertEquals("tag", TAG_1, uid2Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_1, uid2Info.type);
+ assertEquals("duration", TS_3 - TS_2, uid2Info.uptimeMillis);
+ assertEquals("count", 1, uid2Info.completedCount);
+ }
+
+ @Test
+ public void diffTag() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1, TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1, TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ WakelockInfo uid1Info =
+ info.stream().filter(i -> i.tag.equals(TAG_1)).findFirst().orElse(null);
+
+ assertTrue("TAG_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.uid);
+ assertEquals("tag", TAG_1, uid1Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.type);
+ assertEquals("duration", TS_4 - TS_1, uid1Info.uptimeMillis);
+ assertEquals("count", 1, uid1Info.completedCount);
+
+ WakelockInfo uid2Info =
+ info.stream().filter(i -> i.tag.equals(TAG_2)).findFirst().orElse(null);
+ assertTrue("TAG_2 found", uid2Info != null);
+ assertEquals("uid", UID_1, uid2Info.uid);
+ assertEquals("tag", TAG_2, uid2Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_1, uid2Info.type);
+ assertEquals("duration", TS_3 - TS_2, uid2Info.uptimeMillis);
+ assertEquals("count", 1, uid2Info.completedCount);
+ }
+
+ @Test
+ public void diffType() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2, TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2, TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
+
+ ArrayList<WakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ WakelockInfo uid1Info =
+ info.stream().filter(i -> i.type == WAKELOCK_TYPE_1).findFirst().orElse(null);
+
+ assertTrue("WAKELOCK_TYPE_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.uid);
+ assertEquals("tag", TAG_1, uid1Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.type);
+ assertEquals("duration", TS_4 - TS_1, uid1Info.uptimeMillis);
+ assertEquals("count", 1, uid1Info.completedCount);
+
+ WakelockInfo uid2Info =
+ info.stream().filter(i -> i.type == WAKELOCK_TYPE_2).findFirst().orElse(null);
+ assertTrue("WAKELOCK_TYPE_2 found", uid2Info != null);
+ assertEquals("uid", UID_1, uid2Info.uid);
+ assertEquals("tag", TAG_1, uid2Info.tag);
+ assertEquals("type", WAKELOCK_TYPE_2, uid2Info.type);
+ assertEquals("duration", TS_3 - TS_2, uid2Info.uptimeMillis);
+ assertEquals("count", 1, uid2Info.completedCount);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
index 8e221be261e9..827d2f8f04c8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
@@ -159,22 +159,16 @@ public class WifiPowerCalculatorTest {
.isEqualTo(2473);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.3964);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(4001);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.86666);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.866666);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -214,8 +208,6 @@ public class WifiPowerCalculatorTest {
.isEqualTo(12423);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(2.0214666);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -248,22 +240,16 @@ public class WifiPowerCalculatorTest {
/* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.2214666 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(4002);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.27777);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.277777);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -302,8 +288,6 @@ public class WifiPowerCalculatorTest {
.isEqualTo(12423);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(1.0325211);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -349,8 +333,6 @@ public class WifiPowerCalculatorTest {
.isEqualTo(1000);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.8231573);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -371,8 +353,6 @@ public class WifiPowerCalculatorTest {
/* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
private WifiActivityEnergyInfo buildWifiActivityEnergyInfo(long timeSinceBoot,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
new file mode 100644
index 000000000000..cca60339acf7
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats.processor;
+
+import static android.os.BatteryConsumer.POWER_COMPONENT_BASE;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.PowerStatsStore;
+import com.android.server.power.stats.format.BasePowerStatsLayout;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+public class BasePowerStatsProcessorTest {
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+
+ private static AggregatedPowerStatsConfig sAggregatedPowerStatsConfig;
+
+ @BeforeClass
+ public static void setup() {
+ sAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ sAggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_BASE)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessorSupplier(() -> new BasePowerStatsProcessor(() -> 4000));
+ }
+
+ @Test
+ public void processPowerStats() {
+ AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true);
+
+ PowerComponentAggregatedPowerStats stats = aggregatedPowerStats.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_BASE);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BasePowerStatsLayout statsLayout = new BasePowerStatsLayout(descriptor);
+
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_ON));
+ assertThat(statsLayout.getUsageDuration(deviceStats)).isEqualTo(2500);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getUsageDuration(deviceStats)).isEqualTo(8500);
+
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(2500);
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(2500);
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(5000);
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_UNSPECIFIED));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(2500);
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(8500);
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_UNSPECIFIED));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0);
+ }
+
+ @Test
+ public void fuelgaugeAvailable() {
+ AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true);
+
+ PowerComponentAggregatedPowerStats stats = aggregatedPowerStats.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_BASE);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BasePowerStatsLayout statsLayout = new BasePowerStatsLayout(descriptor);
+
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ double dischargeDuration = 0;
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_ON));
+ dischargeDuration += statsLayout.getBatteryDischargeDuration(deviceStats);
+ assertThat(statsLayout.getBatteryDischargePercent(deviceStats)).isWithin(0.1).of(2.5);
+ // (2,000,000 uAh - 900,000 uAh) * 2,500 ms / 11,000 ms
+ assertThat(statsLayout.getBatteryDischargeUah(deviceStats)).isEqualTo(250_000);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER));
+ dischargeDuration += statsLayout.getBatteryDischargeDuration(deviceStats);
+ assertThat(statsLayout.getBatteryDischargePercent(deviceStats)).isWithin(0.1).of(8.5);
+ // (2,000,000 uAh - 900,000 uAh) * 8,500 ms / 11,000 ms
+ assertThat(statsLayout.getBatteryDischargeUah(deviceStats)).isEqualTo(850_000);
+
+ // Allow for rounding errors
+ assertThat(dischargeDuration).isWithin(5).of(6000);
+ }
+
+ @Test
+ public void fuelgaugeUnavailable() {
+ AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(false);
+
+ PowerComponentAggregatedPowerStats stats = aggregatedPowerStats.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_BASE);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BasePowerStatsLayout statsLayout = new BasePowerStatsLayout(descriptor);
+
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ double dischargeDuration = 0;
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_ON));
+ dischargeDuration += statsLayout.getBatteryDischargeDuration(deviceStats);
+ assertThat(statsLayout.getBatteryDischargePercent(deviceStats)).isWithin(0.1).of(2.5);
+ // 11% * 4_000_000 uAh * 2,500 ms / 11,000 ms
+ assertThat(statsLayout.getBatteryDischargeUah(deviceStats)).isEqualTo(100_000);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER));
+ dischargeDuration += statsLayout.getBatteryDischargeDuration(deviceStats);
+ assertThat(statsLayout.getBatteryDischargePercent(deviceStats)).isWithin(0.1).of(8.5);
+ assertThat(statsLayout.getBatteryDischargeUah(deviceStats)).isEqualTo(340_000);
+
+ // Allow for rounding errors
+ assertThat(dischargeDuration).isWithin(5).of(6000);
+ }
+
+ @Test
+ public void exporter() throws Exception {
+ AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true);
+
+ PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
+ PowerStatsAggregator powerStatsAggregator = new PowerStatsAggregator(
+ sAggregatedPowerStatsConfig);
+ PowerStatsExporter exporter = new PowerStatsExporter(powerStatsStore,
+ powerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
+
+ BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0],
+ /* includeProcessStateData */ true,
+ /* includeScreenStateData */ true,
+ /* includesPowerStateData */ true,
+ /* minConsumedPowerThreshold */ 0);
+ exporter.populateBatteryUsageStatsBuilder(builder, aggregatedPowerStats);
+ BatteryUsageStats batteryUsageStats = builder.build();
+
+ List<UidBatteryConsumer> uidBatteryConsumers = batteryUsageStats.getUidBatteryConsumers();
+ UidBatteryConsumer app1 = uidBatteryConsumers.stream()
+ .filter(u->u.getUid() == APP_UID1).findAny().get();
+ assertThat(app1.getUsageDurationMillis(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_BASE, PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.SCREEN_STATE_ON, BatteryConsumer.POWER_STATE_BATTERY)))
+ .isEqualTo(2500);
+ assertThat(app1.getUsageDurationMillis(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_BASE, PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.SCREEN_STATE_OTHER, BatteryConsumer.POWER_STATE_BATTERY)))
+ .isEqualTo(2500);
+ assertThat(app1.getUsageDurationMillis(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_BASE,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.SCREEN_STATE_OTHER, BatteryConsumer.POWER_STATE_BATTERY)))
+ .isEqualTo(5000);
+
+ assertThat(app1.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)).isEqualTo(2500);
+ assertThat(app1.getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)).isEqualTo(2500);
+ assertThat(app1.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(5000);
+ assertThat(app1.getTimeInProcessStateMs(PROCESS_STATE_UNSPECIFIED)).isEqualTo(0);
+
+ UidBatteryConsumer app2 = uidBatteryConsumers.stream()
+ .filter(u->u.getUid() == APP_UID2).findAny().get();
+ assertThat(app2.getUsageDurationMillis(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_BASE, PROCESS_STATE_CACHED,
+ BatteryConsumer.SCREEN_STATE_ON, BatteryConsumer.POWER_STATE_BATTERY)))
+ .isEqualTo(2500);
+ assertThat(app2.getUsageDurationMillis(
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_BASE, PROCESS_STATE_CACHED,
+ BatteryConsumer.SCREEN_STATE_OTHER, BatteryConsumer.POWER_STATE_BATTERY)))
+ .isEqualTo(8500);
+
+ assertThat(app2.getTimeInProcessStateMs(PROCESS_STATE_CACHED)).isEqualTo(11_000);
+ assertThat(app2.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)).isEqualTo(0);
+ assertThat(app2.getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)).isEqualTo(0);
+ assertThat(app2.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(0);
+ assertThat(app2.getTimeInProcessStateMs(PROCESS_STATE_UNSPECIFIED)).isEqualTo(0);
+
+ batteryUsageStats.close();
+ }
+
+ private static AggregatedPowerStats prepareAggregatedPowerStats(boolean fuelgaugeAvailable) {
+ AggregatedPowerStats stats = new AggregatedPowerStats(sAggregatedPowerStatsConfig);
+ stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_BASE);
+ stats.start(0);
+
+ stats.noteBatteryLevel(50, fuelgaugeAvailable ? 2_000_000 : 0, 0);
+
+ stats.setDeviceState(STATE_POWER, POWER_STATE_BATTERY, 0);
+ stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ stats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ // Turn the screen off after 2.5 seconds
+ stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.noteBatteryLevel(45, fuelgaugeAvailable ? 1_400_000 : 0, 3000);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+ stats.noteBatteryLevel(39, fuelgaugeAvailable ? 900_000 : 0, 6000);
+
+ // Kill the app at the 10_000 mark
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_UNSPECIFIED, 10_000);
+
+ stats.noteBatteryLevel(49, fuelgaugeAvailable ? 1_200_000 : 0, 10_000);
+
+ stats.finish(11_000);
+ return stats;
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
index b412ad6edbca..2ff7dde3255f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
@@ -41,7 +41,6 @@ import androidx.annotation.NonNull;
import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.MockClock;
-import com.android.server.power.stats.PowerStatsUidResolver;
import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
import org.junit.Rule;
@@ -63,12 +62,10 @@ public class BinaryStatePowerStatsProcessorTest {
private final MockClock mClock = new MockClock();
private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
- private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
private static class TestBinaryStatePowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- TestBinaryStatePowerStatsProcessor(int powerComponentId,
- double averagePowerMilliAmp, PowerStatsUidResolver uidResolver) {
- super(powerComponentId, uidResolver, averagePowerMilliAmp);
+ TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp) {
+ super(powerComponentId, averagePowerMilliAmp);
}
@Override
@@ -83,7 +80,7 @@ public class BinaryStatePowerStatsProcessorTest {
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
() -> new TestBinaryStatePowerStatsProcessor(
- POWER_COMPONENT, /* averagePowerMilliAmp */ 100, mUidResolver));
+ POWER_COMPONENT, /* averagePowerMilliAmp */ 100));
stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
@@ -161,7 +158,7 @@ public class BinaryStatePowerStatsProcessorTest {
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
() -> new TestBinaryStatePowerStatsProcessor(
- POWER_COMPONENT, /* averagePowerMilliAmp */ 100, mUidResolver));
+ POWER_COMPONENT, /* averagePowerMilliAmp */ 100));
stats.start(0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
index 6dfc22077357..2c580e5ab0d2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
@@ -479,6 +479,7 @@ public class BluetoothPowerStatsProcessorTest {
new AggregatedPowerStats(config).getPowerComponentStats(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
+ aggregatedStats.start(0);
aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
index 0afcbf15415c..23642de466a1 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
@@ -125,7 +125,7 @@ public class CameraPowerStatsTest {
.thenReturn(new int[]{ENERGY_CONSUMER_ID});
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
- () -> new CameraPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+ () -> new CameraPowerStatsProcessor(mStatsRule.getPowerProfile()));
CameraPowerStatsCollector collector = new CameraPowerStatsCollector(mInjector);
collector.addConsumer(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
index 693861539922..cb9d9b12a2fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
@@ -43,6 +43,7 @@ import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.BatteryUsageStatsRule;
import com.android.server.power.stats.format.CpuPowerStatsLayout;
+import com.android.server.power.stats.format.WakelockPowerStatsLayout;
import org.junit.Before;
import org.junit.Rule;
@@ -78,21 +79,37 @@ public class CpuPowerStatsProcessorTest {
.setCpuPowerBracket(0, 1, 1)
.setCpuPowerBracket(2, 0, 2);
- private AggregatedPowerStatsConfig.PowerComponent mConfig;
+ private AggregatedPowerStatsConfig mConfig;
+ private AggregatedPowerStatsConfig.PowerComponent mCpuPowerComponentConfig;
+ private AggregatedPowerStats mAggregatedPowerStats;
private MockPowerComponentAggregatedPowerStats mStats;
+ private WakelockPowerStatsLayout mWakelockStatsLayout;
+ private PowerStats.Descriptor mWakelockDescriptor;
@Before
public void setup() {
- mConfig = new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ mConfig = new AggregatedPowerStatsConfig();
+ mConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WAKELOCK)
.trackDeviceStates(STATE_POWER, STATE_SCREEN)
- .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
+ mCpuPowerComponentConfig = mConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK)
.setProcessorSupplier(() -> new CpuPowerStatsProcessor(mStatsRule.getPowerProfile(),
mStatsRule.getCpuScalingPolicies()));
+ mAggregatedPowerStats = new AggregatedPowerStats(mConfig);
+
+ mWakelockStatsLayout = new WakelockPowerStatsLayout();
+ PersistableBundle extras = new PersistableBundle();
+ mWakelockStatsLayout.toExtras(extras);
+ mWakelockDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+ mWakelockStatsLayout.getDeviceStatsArrayLength(), null, 0,
+ mWakelockStatsLayout.getUidStatsArrayLength(), extras);
}
@Test
public void powerProfileModel() {
- mStats = new MockPowerComponentAggregatedPowerStats(mConfig, false);
+ mStats = new MockPowerComponentAggregatedPowerStats(mAggregatedPowerStats,
+ mCpuPowerComponentConfig, false);
mStats.start(0);
mStats.setDeviceStats(
@@ -136,7 +153,23 @@ public class CpuPowerStatsProcessorTest {
@Test
public void energyConsumerModel() {
- mStats = new MockPowerComponentAggregatedPowerStats(mConfig, true);
+ PowerComponentAggregatedPowerStats wakelockStats =
+ mAggregatedPowerStats.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK);
+
+ wakelockStats.setPowerStatsDescriptor(mWakelockDescriptor);
+ long[] wakelockDeviceStats = new long[mWakelockDescriptor.statsArrayLength];
+ mWakelockStatsLayout.setDevicePowerEstimate(wakelockDeviceStats, 1.000);
+ wakelockStats.setDeviceStats(states(POWER_STATE_BATTERY, SCREEN_STATE_ON),
+ wakelockDeviceStats);
+ long[] wakelockUidStats = new long[mWakelockDescriptor.uidStatsArrayLength];
+ mWakelockStatsLayout.setUidPowerEstimate(wakelockUidStats, 0.100);
+ wakelockStats.setUidStats(42,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND),
+ wakelockUidStats);
+
+ mStats = new MockPowerComponentAggregatedPowerStats(mAggregatedPowerStats,
+ mCpuPowerComponentConfig, true);
mStats.start(0);
mStats.setDeviceStats(
@@ -146,7 +179,7 @@ public class CpuPowerStatsProcessorTest {
values(2000, 1000), // clusters
values(5000), // uptime
values(5_000_000L, 6_000_000L)), // energy, uC
- 3.055555);
+ 2.055555); // 3.055555 - 1.000 (wakelock power)
mStats.setDeviceStats(
states(POWER_STATE_OTHER, SCREEN_STATE_ON),
concat(
@@ -171,7 +204,8 @@ public class CpuPowerStatsProcessorTest {
values(900, 1000, 1500), 1.161902);
mStats.setUidStats(42,
states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND),
- values(600, 500, 300), 0.355406);
+ values(600, 500, 300),
+ 0.255406); // 0.355406 - 0.100 (wakelock power)
mStats.setUidStats(42,
states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
values(1500, 2000, 1000), 0.80773);
@@ -209,10 +243,10 @@ public class CpuPowerStatsProcessorTest {
private final HashMap<String, Double> mExpectedDevicePower = new HashMap<>();
private final HashMap<String, Double> mExpectedUidPower = new HashMap<>();
- MockPowerComponentAggregatedPowerStats(
- AggregatedPowerStatsConfig.PowerComponent config,
+ MockPowerComponentAggregatedPowerStats(AggregatedPowerStats aggregatedPowerStats,
+ AggregatedPowerStatsConfig.PowerComponent powerComponent,
boolean useEnergyConsumers) {
- super(new AggregatedPowerStats(new AggregatedPowerStatsConfig()), config);
+ super(aggregatedPowerStats, powerComponent);
mStatsLayout = new CpuPowerStatsLayout(useEnergyConsumers ? 2 : 0, 2,
new int[]{0, 1, 2});
PersistableBundle extras = new PersistableBundle();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
index e6207d48d8a0..c63267ec6ae7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
@@ -130,7 +130,7 @@ public class GnssPowerStatsTest {
.thenReturn(new int[0]);
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
- () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+ () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile()));
GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
collector.addConsumer(
@@ -220,7 +220,7 @@ public class GnssPowerStatsTest {
.thenReturn(new int[0]);
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
- () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+ () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile()));
stats.noteStateChange(buildHistoryItemInitialStateGpsOn(0));
@@ -299,7 +299,7 @@ public class GnssPowerStatsTest {
.thenReturn(new int[]{ENERGY_CONSUMER_ID});
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
- () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+ () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile()));
GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
collector.addConsumer(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
index 80358c5768f1..4ed44a0563e3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
@@ -182,6 +182,7 @@ public class MobileRadioPowerStatsProcessorTest {
PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+ aggregatedStats.start(0);
aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
@@ -419,6 +420,7 @@ public class MobileRadioPowerStatsProcessorTest {
PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+ aggregatedStats.start(0);
aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
index 024743d9e098..38fe6134d992 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
@@ -45,7 +45,6 @@ import com.android.internal.os.PowerStats;
import com.android.server.power.stats.BatteryUsageStatsRule;
import com.android.server.power.stats.MockClock;
import com.android.server.power.stats.PowerStatsStore;
-import com.android.server.power.stats.PowerStatsUidResolver;
import com.android.server.power.stats.format.CpuPowerStatsLayout;
import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
@@ -132,8 +131,8 @@ public class PowerStatsExporterTest {
null, 0, mCpuStatsArrayLayout.getUidStatsArrayLength(), extras);
mPowerAttributor = new MultiStatePowerAttributor(mock(Context.class), mPowerStatsStore,
- mock(PowerProfile.class), mock(CpuScalingPolicies.class),
- mock(PowerStatsUidResolver.class));
+ mock(PowerProfile.class), mock(CpuScalingPolicies.class), () -> 3500
+ );
}
@Test
@@ -340,11 +339,10 @@ public class PowerStatsExporterTest {
AggregatedPowerStats aps,
boolean includeProcessStateData, boolean includeScreenStateData,
boolean includesPowerStateData) {
- PowerStatsExporter
- exporter = new PowerStatsExporter(mPowerStatsStore,
+ PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
- BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0], false,
+ BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0],
includeProcessStateData, includeScreenStateData, includesPowerStateData, 0);
exporter.populateBatteryUsageStatsBuilder(builder, aps);
return builder.build();
@@ -363,7 +361,7 @@ public class PowerStatsExporterTest {
private void breakdownByProcState_fullRange(boolean includeScreenStateData,
boolean includePowerStateData) throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- new String[]{"cu570m"}, /* includePowerModels */ false,
+ new String[]{"cu570m"},
/* includeProcessStateData */ true, includeScreenStateData,
includePowerStateData, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 1000, 10000);
@@ -408,7 +406,7 @@ public class PowerStatsExporterTest {
@Test
public void breakdownByProcState_subRange() throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- new String[]{"cu570m"}, /* includePowerModels */ false,
+ new String[]{"cu570m"},
/* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 3700, 6700);
@@ -440,7 +438,7 @@ public class PowerStatsExporterTest {
@Test
public void combinedProcessStates() throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- new String[]{"cu570m"}, /* includePowerModels */ false,
+ new String[]{"cu570m"},
/* includeProcessStateData */ false, true, true, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 1000, 10000);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
new file mode 100644
index 000000000000..ed3cda0f76ef
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats.processor;
+
+import static android.os.BatteryConsumer.POWER_COMPONENT_WAKELOCK;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.os.Process;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.format.WakelockPowerStatsLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class WakelockPowerStatsProcessorTest {
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CPU_IDLE, 720);
+
+ public static final int START_TIME = 12345;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final double PRECISION = 0.00001;
+
+ private PowerStatsAggregator mPowerStatsAggregator;
+
+ @Before
+ public void setup() {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WAKELOCK)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessorSupplier(
+ () -> new WakelockPowerStatsProcessor(mStatsRule.getPowerProfile()));
+ mPowerStatsAggregator = new PowerStatsAggregator(config);
+ }
+
+ @Test
+ public void wakelockDuration() {
+ BatteryStatsHistory history = prepareBatteryStatsHistory();
+ mPowerStatsAggregator.aggregatePowerStats(history, 0, Long.MAX_VALUE,
+ this::assertAggregatedPowerStats);
+ }
+
+ private BatteryStatsHistory prepareBatteryStatsHistory() {
+ WakelockPowerStatsLayout statsLayout = new WakelockPowerStatsLayout();
+ PersistableBundle extras = new PersistableBundle();
+ statsLayout.toExtras(extras);
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(
+ BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+ statsLayout.getDeviceStatsArrayLength(), null, 0,
+ statsLayout.getUidStatsArrayLength(), extras);
+ PowerStats ps = new PowerStats(descriptor);
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+
+ BatteryStatsHistory history = new BatteryStatsHistory(null, null, 0, 10000,
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class),
+ mStatsRule.getMockClock(),
+ new MonotonicClock(START_TIME, mStatsRule.getMockClock()), null, null);
+ history.forceRecordAllHistory();
+ history.startRecordingHistory(0, 0, false);
+ history.recordProcessStateChange(0, 0, APP_UID1, PROCESS_STATE_BACKGROUND);
+ history.recordProcessStateChange(0, 0, APP_UID2, PROCESS_STATE_FOREGROUND);
+
+ // Establish a baseline
+ history.recordPowerStats(0, 0, ps);
+
+ ps.durationMs = 5000;
+ statsLayout.setUsageDuration(ps.stats, 2000);
+ statsLayout.setUidUsageDuration(uidStats, 2000);
+ ps.uidStats.put(APP_UID1, uidStats.clone());
+
+ history.recordPowerStats(5000, 2000, ps);
+
+ history.recordProcessStateChange(14000, 11000, APP_UID1, PROCESS_STATE_FOREGROUND_SERVICE);
+
+ history.setPluggedInState(true);
+ history.writeHistoryItem(18500, 15500);
+
+ ps.durationMs = 18000;
+ statsLayout.setUsageDuration(ps.stats, 10000);
+ statsLayout.setUidUsageDuration(uidStats, 2000);
+ ps.uidStats.put(APP_UID1, uidStats.clone());
+ statsLayout.setUidUsageDuration(uidStats, 8000);
+ ps.uidStats.put(APP_UID2, uidStats.clone());
+
+ history.recordPowerStats(23000, 20000, ps);
+
+ return history;
+ }
+
+ private void assertAggregatedPowerStats(AggregatedPowerStats aggregatedPowerStats) {
+ PowerComponentAggregatedPowerStats stats =
+ aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT_WAKELOCK);
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ WakelockPowerStatsLayout statsLayout = new WakelockPowerStatsLayout(descriptor);
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_ON));
+ assertThat(statsLayout.getUsageDuration(deviceStats)).isEqualTo(7500);
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats)).isWithin(PRECISION).of(1.50);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getUsageDuration(deviceStats)).isEqualTo(2500);
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats)).isWithin(PRECISION).of(0.50);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(1000);
+ assertThat(statsLayout.getUidPowerEstimate(uidStats)).isWithin(PRECISION).of(0.20);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(500);
+ assertThat(statsLayout.getUidPowerEstimate(uidStats)).isWithin(PRECISION).of(0.10);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(500);
+ assertThat(statsLayout.getUidPowerEstimate(uidStats)).isWithin(PRECISION).of(0.10);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(6000);
+ assertThat(statsLayout.getUidPowerEstimate(uidStats)).isWithin(PRECISION).of(1.20);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(2000);
+ assertThat(statsLayout.getUidPowerEstimate(uidStats)).isWithin(PRECISION).of(0.40);
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
index baf468e3f7de..1e097692f55e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
@@ -536,6 +536,7 @@ public class WifiPowerStatsProcessorTest {
PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_WIFI);
+ aggregatedStats.start(0);
aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index 0dc836ba0400..fe4d971face5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -17,6 +17,7 @@
package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_BLUETOOTH;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
@@ -52,6 +53,7 @@ public class CpuWakeupStatsTest {
private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device";
private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device";
private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device";
+ private static final String KERNEL_REASON_BLUETOOTH_IRQ = "19 test.bluetooth.device";
private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
private static final String KERNEL_REASON_UNKNOWN_FORMAT = "free-form-reason test.alarm.device";
private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
@@ -62,12 +64,14 @@ public class CpuWakeupStatsTest {
private static final int TEST_UID_3 = 92261423;
private static final int TEST_UID_4 = 56926423;
private static final int TEST_UID_5 = 76421423;
+ private static final int TEST_UID_6 = 62345353;
private static final int TEST_PROC_STATE_1 = 72331;
private static final int TEST_PROC_STATE_2 = 792351;
private static final int TEST_PROC_STATE_3 = 138831;
private static final int TEST_PROC_STATE_4 = 23231;
private static final int TEST_PROC_STATE_5 = 42;
+ private static final int TEST_PROC_STATE_6 = 129942;
private static final Context sContext = InstrumentationRegistry.getTargetContext();
private final Handler mHandler = Mockito.mock(Handler.class);
@@ -79,6 +83,7 @@ public class CpuWakeupStatsTest {
obj.mUidProcStates.put(TEST_UID_3, TEST_PROC_STATE_3);
obj.mUidProcStates.put(TEST_UID_4, TEST_PROC_STATE_4);
obj.mUidProcStates.put(TEST_UID_5, TEST_PROC_STATE_5);
+ obj.mUidProcStates.put(TEST_UID_6, TEST_PROC_STATE_6);
}
@Test
@@ -118,6 +123,7 @@ public class CpuWakeupStatsTest {
CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
CPU_WAKEUP_SUBSYSTEM_SENSOR,
CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
+ CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
};
final String[] kernelReasons = new String[] {
@@ -126,10 +132,11 @@ public class CpuWakeupStatsTest {
KERNEL_REASON_SOUND_TRIGGER_IRQ,
KERNEL_REASON_SENSOR_IRQ,
KERNEL_REASON_CELLULAR_DATA_IRQ,
+ KERNEL_REASON_BLUETOOTH_IRQ,
};
final int[] uids = new int[] {
- TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5
+ TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5, TEST_UID_6
};
final int[] procStates = new int[] {
@@ -137,7 +144,8 @@ public class CpuWakeupStatsTest {
TEST_PROC_STATE_3,
TEST_PROC_STATE_4,
TEST_PROC_STATE_1,
- TEST_PROC_STATE_5
+ TEST_PROC_STATE_5,
+ TEST_PROC_STATE_6
};
final int total = subsystems.length;
@@ -285,6 +293,40 @@ public class CpuWakeupStatsTest {
}
@Test
+ public void bluetoothIrqAttributionSolo() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ final long wakeupTime = 1236121;
+
+ populateDefaultProcStates(obj);
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_BLUETOOTH_IRQ);
+
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
+ wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
+ wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime + 5, TEST_UID_3,
+ TEST_UID_5);
+
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey(
+ TEST_UID_1)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey(
+ TEST_UID_2)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey(
+ TEST_UID_4)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
+ }
+
+ @Test
public void alarmAndWifiIrqAttribution() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123210;
@@ -400,6 +442,47 @@ public class CpuWakeupStatsTest {
}
@Test
+ public void unknownAndBluetoothAttribution() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ final long wakeupTime = 92123520;
+
+ populateDefaultProcStates(obj);
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 24,
+ KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_BLUETOOTH_IRQ);
+
+ // Bluetooth activity
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
+ wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime + 2, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime - 1, TEST_UID_3,
+ TEST_UID_5);
+
+ // Unrelated, should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(2);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_1)).isEqualTo(
+ TEST_PROC_STATE_1);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)
+ .indexOfKey(TEST_UID_2)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)
+ .indexOfKey(TEST_UID_4)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isNull();
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isFalse();
+ }
+
+ @Test
public void unknownFormatWakeupIgnored() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 72123210;
diff --git a/services/tests/security/forensic/OWNERS b/services/tests/security/forensic/OWNERS
deleted file mode 100644
index 80c9afb96033..000000000000
--- a/services/tests/security/forensic/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 36824
-
-file:platform/frameworks/base:main:/core/java/android/security/forensic/OWNERS
diff --git a/services/tests/security/intrusiondetection/Android.bp b/services/tests/security/intrusiondetection/Android.bp
new file mode 100644
index 000000000000..8d674b14feac
--- /dev/null
+++ b/services/tests/security/intrusiondetection/Android.bp
@@ -0,0 +1,49 @@
+package {
+ default_team: "trendy_team_platform_security",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "IntrusionDetectionServiceTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "compatibility-device-util-axt",
+ "coretests-aidl",
+ "frameworks-base-testutils",
+ "junit",
+ "platform-test-annotations",
+ "servicestests-utils",
+ "services.core",
+ "truth",
+ "Nene",
+ "Harrier",
+ "TestApp",
+ ],
+ data: [
+ ":TestIntrusionDetectionApp",
+ ],
+
+ platform_apis: true,
+
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
new file mode 100644
index 000000000000..d58e0d84ee32
--- /dev/null
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.security.intrusiondetection.tests">
+
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
+
+ <application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <queries>
+ <package android:name="com.android.coretests.apps.testapp" />
+ </queries>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.security.intrusiondetection.tests"
+ android:label="Frameworks IntrusionDetection Services Tests"/>
+</manifest>
diff --git a/services/tests/security/intrusiondetection/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml
new file mode 100644
index 000000000000..0d211585958a
--- /dev/null
+++ b/services/tests/security/intrusiondetection/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Frameworks IntrusionDetection Service tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="IntrusionDetectionServiceTests.apk"/>
+ <option name="test-file-name" value="TestIntrusionDetectionApp.apk"/>
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop intrusiondetection_service_name com.android.coretests.apps.testapp/.TestLoggingService" />
+ </target_preparer>
+
+ <option name="test-tag" value="IntrusionDetectionServiceTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.server.security.intrusiondetection.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/security/intrusiondetection/OWNERS b/services/tests/security/intrusiondetection/OWNERS
new file mode 100644
index 000000000000..2157972e06e8
--- /dev/null
+++ b/services/tests/security/intrusiondetection/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+file:platform/frameworks/base:main:/core/java/android/security/intrusiondetection/OWNERS
diff --git a/services/tests/security/intrusiondetection/TEST_MAPPING b/services/tests/security/intrusiondetection/TEST_MAPPING
new file mode 100644
index 000000000000..24d63e36e32b
--- /dev/null
+++ b/services/tests/security/intrusiondetection/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "IntrusionDetectionServiceTests"
+ }
+ ]
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
new file mode 100644
index 000000000000..5cba6b2c3e67
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -0,0 +1,673 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
+import static android.Manifest.permission.INTERNET;
+import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
+import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
+
+import static org.junit.Assert.assertEquals;
+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;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.SuppressLint;
+import android.app.admin.ConnectEvent;
+import android.app.admin.DnsEvent;
+import android.app.admin.SecurityLog;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PermissionEnforcer;
+import android.os.RemoteException;
+import android.os.test.FakePermissionEnforcer;
+import android.os.test.TestLooper;
+import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback;
+import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.devicepolicy.DeviceOwner;
+import com.android.bedstead.permissions.CommonPermissions;
+import com.android.bedstead.permissions.PermissionContext;
+import com.android.bedstead.permissions.annotations.EnsureHasPermission;
+import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport;
+import com.android.server.ServiceThread;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(BedsteadJUnit4.class)
+public class IntrusionDetectionServiceTest {
+ private static final int STATE_UNKNOWN =
+ IIntrusionDetectionServiceStateCallback.State.UNKNOWN;
+ private static final int STATE_DISABLED =
+ IIntrusionDetectionServiceStateCallback.State.DISABLED;
+ private static final int STATE_ENABLED =
+ IIntrusionDetectionServiceStateCallback.State.ENABLED;
+
+ private static final int ERROR_UNKNOWN =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.UNKNOWN;
+ private static final int ERROR_PERMISSION_DENIED =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
+ private static final int ERROR_TRANSPORT_UNAVAILABLE =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE;
+ private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
+ IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+
+ private static DeviceOwner sDeviceOwner;
+
+ private Context mContext;
+ private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection;
+ private DataAggregator mDataAggregator;
+ private IntrusionDetectionService mIntrusionDetectionService;
+ private IBinder mService;
+ private TestLooper mTestLooper;
+ private Looper mLooper;
+ private TestLooper mTestLooperOfDataAggregator;
+ private Looper mLooperOfDataAggregator;
+ private FakePermissionEnforcer mPermissionEnforcer;
+ private boolean mBoundToLoggingService = false;
+ private static final String TEST_PKG =
+ "com.android.coretests.apps.testapp";
+ private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService";
+
+ @SuppressLint("VisibleForTests")
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
+
+ mPermissionEnforcer = new FakePermissionEnforcer();
+ mPermissionEnforcer.grant(READ_INTRUSION_DETECTION_STATE);
+ mPermissionEnforcer.grant(MANAGE_INTRUSION_DETECTION_STATE);
+ mPermissionEnforcer.grant(BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE);
+
+ mTestLooper = new TestLooper();
+ mLooper = mTestLooper.getLooper();
+ mTestLooperOfDataAggregator = new TestLooper();
+ mLooperOfDataAggregator = mTestLooperOfDataAggregator.getLooper();
+ mIntrusionDetectionService = new IntrusionDetectionService(new MockInjector(mContext));
+ mIntrusionDetectionService.onStart();
+ }
+
+ @Test
+ public void testAddStateCallback_NoPermission() {
+ mPermissionEnforcer.revoke(READ_INTRUSION_DETECTION_STATE);
+ StateCallback scb = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb.mState);
+ assertThrows(SecurityException.class,
+ () -> mIntrusionDetectionService.getBinderService().addStateCallback(scb));
+ }
+
+ @Test
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testRemoveStateCallback_NoPermission() {
+ mPermissionEnforcer.revoke(READ_INTRUSION_DETECTION_STATE);
+ StateCallback scb = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb.mState);
+ assertThrows(SecurityException.class,
+ () -> mIntrusionDetectionService.getBinderService().removeStateCallback(scb));
+ }
+
+ @Test
+ public void testEnable_NoPermission() {
+ mPermissionEnforcer.revoke(MANAGE_INTRUSION_DETECTION_STATE);
+
+ CommandCallback ccb = new CommandCallback();
+ assertThrows(SecurityException.class,
+ () -> mIntrusionDetectionService.getBinderService().enable(ccb));
+ }
+
+ @Test
+ public void testDisable_NoPermission() {
+ mPermissionEnforcer.revoke(MANAGE_INTRUSION_DETECTION_STATE);
+
+ CommandCallback ccb = new CommandCallback();
+ assertThrows(SecurityException.class,
+ () -> mIntrusionDetectionService.getBinderService().disable(ccb));
+ }
+
+ @Test
+ public void testAddStateCallback_Disabled() throws RemoteException {
+ StateCallback scb = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb.mState);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb.mState);
+ }
+
+ @Test
+ public void testAddStateCallback_Disabled_TwoStateCallbacks() throws RemoteException {
+ StateCallback scb1 = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb1.mState);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+
+ StateCallback scb2 = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb2.mState);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb2.mState);
+ }
+
+ @Test
+ @Ignore("Unit test does not run as system service UID")
+ public void testRemoveStateCallback() throws RemoteException {
+ mIntrusionDetectionService.setState(STATE_DISABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+
+ doReturn(true).when(mIntrusionDetectionEventTransportConnection).initialize();
+
+ mIntrusionDetectionService.getBinderService().removeStateCallback(scb2);
+
+ CommandCallback ccb = new CommandCallback();
+ mIntrusionDetectionService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Ignore("Unit test does not run as system service UID")
+ @Test
+ public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException {
+ mIntrusionDetectionService.setState(STATE_DISABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+
+ doReturn(true).when(mIntrusionDetectionEventTransportConnection).initialize();
+
+ CommandCallback ccb = new CommandCallback();
+ mIntrusionDetectionService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+
+ verify(mDataAggregator, times(1)).enable();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testEnable_FromEnabled_TwoStateCallbacks()
+ throws RemoteException {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mIntrusionDetectionService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testDisable_FromDisabled_TwoStateCallbacks() throws RemoteException {
+ mIntrusionDetectionService.setState(STATE_DISABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mIntrusionDetectionService.getBinderService().disable(ccb);
+ mTestLooper.dispatchAll();
+
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testDisable_FromEnabled_TwoStateCallbacks() throws RemoteException {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+
+ doNothing().when(mIntrusionDetectionEventTransportConnection).release();
+
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+ CommandCallback ccb = new CommandCallback();
+ mIntrusionDetectionService.getBinderService().disable(ccb);
+ mTestLooper.dispatchAll();
+ mTestLooperOfDataAggregator.dispatchAll();
+ // TODO: We can verify the data sources once we implement them.
+ verify(mockThread, times(1)).quitSafely();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Ignore("Enable once the IntrusionDetectionEventTransportConnection is ready")
+ @Test
+ public void testEnable_FromDisable_TwoStateCallbacks_TransportUnavailable()
+ throws RemoteException {
+ mIntrusionDetectionService.setState(STATE_DISABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb1);
+ mIntrusionDetectionService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+
+ doReturn(false).when(mIntrusionDetectionEventTransportConnection).initialize();
+
+ CommandCallback ccb = new CommandCallback();
+ mIntrusionDetectionService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue());
+ }
+
+ @Test
+ public void testDataAggregator_AddBatchData() {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+ SecurityEvent securityEvent = new SecurityEvent(0, new byte[0]);
+ IntrusionDetectionEvent eventOne = new IntrusionDetectionEvent(securityEvent);
+
+ ConnectEvent connectEvent = new ConnectEvent(
+ "127.0.0.1", 80, null, 0);
+ IntrusionDetectionEvent eventTwo = new IntrusionDetectionEvent(connectEvent);
+
+ DnsEvent dnsEvent = new DnsEvent(
+ null, new String[] {"127.0.0.1"}, 1, null, 0);
+ IntrusionDetectionEvent eventThree = new IntrusionDetectionEvent(dnsEvent);
+
+ List<IntrusionDetectionEvent> events = new ArrayList<>();
+ events.add(eventOne);
+ events.add(eventTwo);
+ events.add(eventThree);
+
+ doReturn(true).when(mIntrusionDetectionEventTransportConnection).addData(any());
+
+ mDataAggregator.addBatchData(events);
+ mTestLooperOfDataAggregator.dispatchAll();
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<List<IntrusionDetectionEvent>> captor = ArgumentCaptor.forClass(List.class);
+ verify(mIntrusionDetectionEventTransportConnection).addData(captor.capture());
+ List<IntrusionDetectionEvent> receivedEvents = captor.getValue();
+ assertEquals(receivedEvents.size(), 3);
+
+ assertEquals(receivedEvents.get(0).getType(), IntrusionDetectionEvent.SECURITY_EVENT);
+ assertNotNull(receivedEvents.get(0).getSecurityEvent());
+
+ assertEquals(receivedEvents.get(1).getType(),
+ IntrusionDetectionEvent.NETWORK_EVENT_CONNECT);
+ assertNotNull(receivedEvents.get(1).getConnectEvent());
+
+ assertEquals(receivedEvents.get(2).getType(), IntrusionDetectionEvent.NETWORK_EVENT_DNS);
+ assertNotNull(receivedEvents.get(2).getDnsEvent());
+ }
+
+ @Test
+ @Ignore("Unit test does not run as system service UID")
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testDataAggregator_AddSecurityEvent() throws Exception {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+ // SecurityLogging generates a number of events and callbacks, so create a latch to wait for
+ // the given event.
+ String eventString = this.getClass().getName() + ".testSecurityEvent";
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ List<IntrusionDetectionEvent> receivedEvents =
+ (List<IntrusionDetectionEvent>) input.getArguments()[0];
+ for (IntrusionDetectionEvent event : receivedEvents) {
+ if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
+ SecurityEvent securityEvent = event.getSecurityEvent();
+ Object[] eventData = (Object[]) securityEvent.getData();
+ if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
+ && eventData[1].equals(eventString)) {
+ latch.countDown();
+ }
+ }
+ }
+ return true;
+ }
+ })
+ .when(mIntrusionDetectionEventTransportConnection).addData(any());
+ mDataAggregator.enable();
+
+ // Generate the security event.
+ generateSecurityEvent(eventString);
+ TestApis.devicePolicy().forceSecurityLogs();
+
+ // Verify the event is received.
+ mTestLooper.startAutoDispatch();
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ mTestLooper.stopAutoDispatch();
+
+ mDataAggregator.disable();
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @Ignore("Unit test does not run as system service UID")
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testDataAggregator_AddNetworkEvent() throws Exception {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+ // Network logging may log multiple and callbacks, so create a latch to wait for
+ // the given event.
+ // eventServer must be a valid domain to generate a network log event.
+ String eventServer = "google.com";
+ final CountDownLatch latch = new CountDownLatch(1);
+ // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ List<IntrusionDetectionEvent> receivedEvents =
+ (List<IntrusionDetectionEvent>) input.getArguments()[0];
+ for (IntrusionDetectionEvent event : receivedEvents) {
+ if (event.getType()
+ == IntrusionDetectionEvent.NETWORK_EVENT_DNS) {
+ DnsEvent dnsEvent = event.getDnsEvent();
+ if (dnsEvent.getHostname().equals(eventServer)) {
+ latch.countDown();
+ }
+ }
+ }
+ return true;
+ }
+ })
+ .when(mIntrusionDetectionEventTransportConnection).addData(any());
+ mDataAggregator.enable();
+
+ // Generate the network event.
+ generateNetworkEvent(eventServer);
+ TestApis.devicePolicy().forceNetworkLogs();
+
+ // Verify the event is received.
+ mTestLooper.startAutoDispatch();
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ mTestLooper.stopAutoDispatch();
+
+ mDataAggregator.disable();
+ }
+
+ /** Emits a given string into security log (if enabled). */
+ private void generateSecurityEvent(String eventString)
+ throws IllegalArgumentException, GeneralSecurityException, IOException {
+ if (eventString == null || eventString.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Error generating security event: eventString must not be empty");
+ }
+
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+ keyGen.initialize(
+ new KeyGenParameterSpec.Builder(eventString, KeyProperties.PURPOSE_SIGN).build());
+ // Emit key generation event.
+ final KeyPair keyPair = keyGen.generateKeyPair();
+ assertNotNull(keyPair);
+
+ final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+ ks.load(null);
+ // Emit key destruction event.
+ ks.deleteEntry(eventString);
+ }
+
+ /** Emits a given string into network log (if enabled). */
+ private void generateNetworkEvent(String server) throws IllegalArgumentException, IOException {
+ if (server == null || server.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Error generating network event: server must not be empty");
+ }
+
+ HttpURLConnection urlConnection = null;
+ int connectionTimeoutMS = 2_000;
+ try (PermissionContext p = TestApis.permissions().withPermission(INTERNET)) {
+ final URL url = new URL("http://" + server);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setConnectTimeout(connectionTimeoutMS);
+ urlConnection.setReadTimeout(connectionTimeoutMS);
+ urlConnection.getResponseCode();
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+ public void test_StartIntrusionDetectionEventTransportService() {
+ final String TAG = "test_StartIntrusionDetectionEventTransportService";
+ ServiceConnection serviceConnection = null;
+
+ assertEquals(false, mBoundToLoggingService);
+ try {
+ serviceConnection = startTestService();
+ assertEquals(true, mBoundToLoggingService);
+ assertNotNull(serviceConnection);
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException while starting: ", e);
+ fail("Exception thrown while connecting to service");
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException while starting: ", e);
+ fail("Interrupted while connecting to service");
+ } finally {
+ mContext.unbindService(serviceConnection);
+ }
+ }
+
+ private ServiceConnection startTestService() throws SecurityException, InterruptedException {
+ final String TAG = "startTestService";
+ final CountDownLatch latch = new CountDownLatch(1);
+ LocalIntrusionDetectionEventTransport transport =
+ new LocalIntrusionDetectionEventTransport();
+
+ ServiceConnection serviceConnection = new ServiceConnection() {
+ // Called when connection with the service is established.
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mService = transport.getBinder();
+ mBoundToLoggingService = true;
+ latch.countDown();
+ }
+
+ // Called when the connection with the service disconnects unexpectedly.
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "onServiceDisconnected");
+ mBoundToLoggingService = false;
+ }
+ };
+
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+ mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ latch.await(5, TimeUnit.SECONDS);
+
+ // call the methods on the transport object
+ IntrusionDetectionEvent event =
+ new IntrusionDetectionEvent(new SecurityEvent(123, new byte[15]));
+ List<IntrusionDetectionEvent> events = new ArrayList<>();
+ events.add(event);
+ assertTrue(transport.initialize());
+ assertTrue(transport.addData(events));
+ assertTrue(transport.release());
+ assertEquals(1, transport.getEvents().size());
+
+ return serviceConnection;
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+ public void testIntrusionDetectionEventTransportConnection_isValidAndBinds()
+ throws InterruptedException {
+ IntrusionDetectionEventTransportConnection intrusionDetectionEventTransportConnection =
+ new IntrusionDetectionEventTransportConnection(mContext);
+ // In a real scenario, the connection will be initialized by the service.
+ // Just to show that the connection is valid and able to bind,
+ // we initialize it here.
+ assertTrue(intrusionDetectionEventTransportConnection.initialize());
+ }
+
+ private class MockInjector implements IntrusionDetectionService.Injector {
+ private final Context mContext;
+
+ MockInjector(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public PermissionEnforcer getPermissionEnforcer() {
+ return mPermissionEnforcer;
+ }
+
+ @Override
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ @Override
+ public IntrusionDetectionEventTransportConnection
+ getIntrusionDetectionEventransportConnection() {
+ mIntrusionDetectionEventTransportConnection =
+ spy(new IntrusionDetectionEventTransportConnection(mContext));
+ return mIntrusionDetectionEventTransportConnection;
+ }
+
+ @Override
+ public DataAggregator getDataAggregator(
+ IntrusionDetectionService intrusionDetectionService) {
+ mDataAggregator = spy(new DataAggregator(mContext, intrusionDetectionService));
+ return mDataAggregator;
+ }
+ }
+
+ private static class StateCallback extends IIntrusionDetectionServiceStateCallback.Stub {
+ int mState = STATE_UNKNOWN;
+
+ @Override
+ public void onStateChange(int state) throws RemoteException {
+ mState = state;
+ }
+ }
+
+ private static class CommandCallback extends IIntrusionDetectionServiceCommandCallback.Stub {
+ Integer mErrorCode = null;
+
+ public void reset() {
+ mErrorCode = null;
+ }
+
+ @Override
+ public void onSuccess() throws RemoteException {
+
+ }
+
+ @Override
+ public void onFailure(int errorCode) throws RemoteException {
+ mErrorCode = errorCode;
+ }
+ }
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
new file mode 100644
index 000000000000..ca5952b140c1
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_platform_security",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "TestIntrusionDetectionApp",
+
+ static_libs: [
+ "frameworks-base-testutils",
+ "services.core",
+ "servicestests-utils",
+ ],
+
+ srcs: ["**/*.java"],
+
+ platform_apis: true,
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
new file mode 100644
index 000000000000..a1a7e29094d2
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.coretests.apps.testapp">
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
+
+ <application>
+ <service android:name=".TestLoggingService"
+ android:exported="true"
+ android:permission="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
+ </application>
+</manifest> \ No newline at end of file
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
new file mode 100644
index 000000000000..f0012da44fa4
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance
+ with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+
+ */
+
+package com.android.coretests.apps.testapp;
+
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IntrusionDetectionEventTransport;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that extends {@link IntrusionDetectionEventTransport} to provide a
+ * local transport mechanism for testing purposes. This implementation overrides
+ * the {@link #initialize()}, {@link #addData(List)}, and {@link #release()} methods
+ * to manage events locally within the test environment.
+ *
+ * For now, the implementation returns true for all methods since we don't
+ * have a real data source to send events to.
+ */
+public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEventTransport {
+ private List<IntrusionDetectionEvent> mEvents = new ArrayList<>();
+
+ @Override
+ public boolean initialize() {
+ return true;
+ }
+
+ @Override
+ public boolean addData(List<IntrusionDetectionEvent> events) {
+ mEvents.addAll(events);
+ return true;
+ }
+
+ @Override
+ public boolean release() {
+ return true;
+ }
+
+ public List<IntrusionDetectionEvent> getEvents() {
+ return mEvents;
+ }
+} \ No newline at end of file
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
new file mode 100644
index 000000000000..e4bf987402fd
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.coretests.apps.testapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+
+import com.android.internal.infra.AndroidFuture;
+
+
+public class TestLoggingService extends Service {
+ private static final String TAG = "TestLoggingService";
+ private LocalIntrusionDetectionEventTransport mLocalIntrusionDetectionEventTransport;
+
+ public TestLoggingService() {
+ mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport();
+ }
+
+ // Binder given to clients.
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mLocalIntrusionDetectionEventTransport.getBinder();
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d8b5a07ae9f0..d702cae248a9 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -49,8 +49,10 @@ android_test {
"services.credentials",
"services.devicepolicy",
"services.flags",
+ "com.android.server.flags.services-aconfig-java",
"services.net",
"services.people",
+ "services.supervision",
"services.usage",
"service-permission.stubs.system_server",
"guava",
@@ -62,6 +64,7 @@ android_test {
"androidx.test.ext.junit",
"cts-wm-util",
"platform-compat-test-rules",
+ "platform-parametric-runner-lib",
"mockito-target-minus-junit4",
"mockito-kotlin2",
"platform-test-annotations",
@@ -77,6 +80,7 @@ android_test {
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
+ "flag-junit",
"junit",
"junit-params",
"ActivityContext",
@@ -89,7 +93,10 @@ android_test {
"CtsVirtualDeviceCommonLib",
"com_android_server_accessibility_flags_lib",
"locksettings_flags_lib",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": ["service-crashrecovery-pre-jarjar"],
+ default: [],
+ }),
libs: [
"android.hardware.power-V1-java",
@@ -887,16 +894,6 @@ test_module_config {
}
test_module_config {
- name: "FrameworksServicesTests_server_accessibility",
- base: "FrameworksServicesTests",
- test_suites: [
- "automotive-tests",
- "device-tests",
- ],
- include_filters: ["com.android.server.accessibility"],
-}
-
-test_module_config {
name: "FrameworksServicesTests_server_binarytransparencyservicetest",
base: "FrameworksServicesTests",
test_suites: [
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 2724149d859f..9b7bbe04132c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -113,6 +113,8 @@
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
+ <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index ae78dfe624c6..cc5be7ebba62 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -40,6 +41,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -50,6 +52,12 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.os.IBinaryTransparencyService;
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageStateInternal;
import org.junit.After;
import org.junit.Assert;
@@ -68,6 +76,9 @@ import java.util.List;
public class BinaryTransparencyServiceTest {
private static final String TAG = "BinaryTransparencyServiceTest";
+ private static final String TEST_PKG_NAME = "testPackageName";
+ private static final long TEST_VERSION_CODE = 1L;
+
private Context mContext;
private BinaryTransparencyService mBinaryTransparencyService;
private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface;
@@ -83,6 +94,8 @@ public class BinaryTransparencyServiceTest {
private PackageManager mPackageManager;
@Mock
private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private BinaryTransparencyService.BicCallbackHandler.IBicAppInfoHelper mBicAppInfoHelper;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
@@ -91,6 +104,9 @@ public class BinaryTransparencyServiceTest {
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
mFaceAuthenticatorsRegisteredCaptor;
+ @Captor
+ private ArgumentCaptor<IBinaryTransparencyService.AppInfo> appInfoCaptor;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -262,4 +278,69 @@ public class BinaryTransparencyServiceTest {
eq("") /* softwareVersion */
);
}
+
+ @Test
+ public void BicCallbackHandler_uploads_mba_metrics() {
+ Bundle data = setupBicCallbackHandlerTest(false,
+ BinaryTransparencyService.MBA_STATUS_NEW_INSTALL);
+
+ BinaryTransparencyService.BicCallbackHandler handler =
+ new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+ handler.sendResult(data);
+
+ verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+ Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+ Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+ }
+
+ @Test
+ public void BicCallbackHandler_uploads_mba_metrics_for_preloads() {
+ Bundle data = setupBicCallbackHandlerTest(true,
+ BinaryTransparencyService.MBA_STATUS_UPDATED_PRELOAD);
+
+ BinaryTransparencyService.BicCallbackHandler handler =
+ new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+ handler.sendResult(data);
+
+ verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+ Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+ Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+ }
+
+ @Test
+ public void BicCallbackHandler_uploads_mba_metrics_for_uninstalls() {
+ Bundle data = new Bundle();
+ data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+ TEST_PKG_NAME);
+ data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
+
+ BinaryTransparencyService.BicCallbackHandler handler =
+ new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+ handler.sendResult(data);
+
+ verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+ Assert.assertEquals(TEST_PKG_NAME ,appInfoCaptor.getValue().packageName);
+ Assert.assertEquals(BinaryTransparencyService.MBA_STATUS_UNINSTALLED,
+ appInfoCaptor.getValue().mbaStatus);
+ }
+
+ private Bundle setupBicCallbackHandlerTest(boolean isUpdatedSystemApp,
+ int expectedBtsMbaStatus) {
+ Bundle data = new Bundle();
+ data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+ TEST_PKG_NAME);
+ data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL);
+ PackageStateInternal mockPackageState = mock(PackageStateInternal.class);
+ when(mPackageManagerInternal.getPackageStateInternal(TEST_PKG_NAME))
+ .thenReturn(mockPackageState);
+ when(mockPackageState.isUpdatedSystemApp()).thenReturn(isUpdatedSystemApp);
+ IBinaryTransparencyService.AppInfo appInfo = new IBinaryTransparencyService.AppInfo();
+ appInfo.packageName = TEST_PKG_NAME;
+ appInfo.longVersion = TEST_VERSION_CODE;
+ when(mBicAppInfoHelper.collectAppInfo(mockPackageState, expectedBtsMbaStatus))
+ .thenReturn(List.of(appInfo));
+ return data;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index ec78bcea7539..c18faef2c028 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -31,6 +31,9 @@ import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.testing.TestableContext;
@@ -43,6 +46,9 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.flags.Flags;
+import com.android.server.pinner.PinnedFile;
+import com.android.server.pinner.PinnerService;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -73,15 +79,18 @@ public class PinnerServiceTest {
private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+ private static final int MEMORY_PERCENTAGE_FOR_QUOTA = 10;
+
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final ArraySet<String> mUpdatedPackages = new ArraySet<>();
private ResolveInfo mHomePackageResolveInfo;
private FakeDeviceConfigInterface mFakeDeviceConfigInterface;
private PinnerService.Injector mInjector;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -114,6 +123,8 @@ public class PinnerServiceTest {
resources.addOverride(com.android.internal.R.bool.config_pinnerCameraApp, false);
resources.addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 0);
resources.addOverride(com.android.internal.R.bool.config_pinnerAssistantApp, false);
+ resources.addOverride(com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage,
+ MEMORY_PERCENTAGE_FOR_QUOTA);
mFakeDeviceConfigInterface = new FakeDeviceConfigInterface();
setDeviceConfigPinnedAnonSize(0);
@@ -138,10 +149,9 @@ public class PinnerServiceTest {
}
@Override
- protected PinnerService.PinnedFile pinFileInternal(String fileToPin,
- int maxBytesToPin, boolean attemptPinIntrospection) {
- return new PinnerService.PinnedFile(-1,
- maxBytesToPin, fileToPin, maxBytesToPin);
+ protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
+ long maxBytesToPin, boolean attemptPinIntrospection) {
+ return new PinnedFile(-1, maxBytesToPin, fileToPin, maxBytesToPin);
}
};
}
@@ -167,6 +177,12 @@ public class PinnerServiceTest {
unpinAnonRegionMethod.invoke(pinnerService);
}
+ private long getGlobalPinQuota(PinnerService service) throws Exception {
+ Method getQuotaMethod = PinnerService.class.getDeclaredMethod("getAvailableGlobalQuota");
+ getQuotaMethod.setAccessible(true);
+ return (long) getQuotaMethod.invoke(service);
+ }
+
private void waitForPinnerService(PinnerService pinnerService)
throws NoSuchFieldException, IllegalAccessException {
// There's no notification/callback when pinning finished
@@ -315,15 +331,121 @@ public class PinnerServiceTest {
PinnerService pinnerService = new PinnerService(mContext, mInjector);
pinnerService.onStart();
- pinnerService.pinFile("test_file", 4096, null, "my_group");
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
- assertThat(getPinnedSize(pinnerService)).isGreaterThan(0);
- assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0);
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(4096);
+ assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(1);
+
+ unpinAll(pinnerService);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testPinAllQuota() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ long quota = getGlobalPinQuota(pinnerService);
+
+ pinnerService.pinFile("test_file", Long.MAX_VALUE, null, "my_group", false);
+
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(quota);
unpinAll(pinnerService);
}
@Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinQuotaAsDevicePercentage() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+ long origQuota = getGlobalPinQuota(pinnerService);
+
+ long totalMem = android.os.Process.getTotalMemory();
+
+ // Verify that pin quota is the set percentage of device total memory
+ assertThat(origQuota).isEqualTo((totalMem * MEMORY_PERCENTAGE_FOR_QUOTA) / 100);
+
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota - 4096);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinWhenNoQuota() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // Verify that pin quota is zero
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0);
+
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
+ assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(0);
+ }
+
+ /**
+ * This test is temporary, it should be cleaned up when removing the pin_global_quota bugfix
+ * flag.
+ */
+ @Test
+ @DisableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalQuotaDisabled() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // The quota parameter exists but it should have no effect on pinning
+ long quota = getGlobalPinQuota(pinnerService);
+
+ pinnerService.pinFile("test_file", quota + 1, null, "my_group", false);
+
+ // Verify that we can pin past the quota as it is disabled
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(quota + 1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testUnpinReleasesQuota() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+ long origQuota = getGlobalPinQuota(pinnerService);
+
+ // Verify that pin quota exists and is non zero.
+ assertThat(getGlobalPinQuota(pinnerService)).isGreaterThan(0);
+
+ pinnerService.pinFile("test_file", origQuota, null, "my_group", false);
+
+ // Make sure all the quota was consumed
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(origQuota);
+
+ // Unpin the file and verify that the quota has been released.
+ pinnerService.unpinFile("test_file");
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(0);
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinQuotaNegative() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, -10);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // Verify that pin quota is zero
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0);
+ }
+
+ @Test
public void testPinAnonRegion() throws Exception {
setDeviceConfigPinnedAnonSize(32768);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 6e6d5a870031..8dfd54fe38bc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -174,8 +174,8 @@ public class AbstractAccessibilityServiceConnectionTest {
@Mock private AccessibilityTrace mMockA11yTrace;
@Mock private WindowManagerInternal mMockWindowManagerInternal;
@Mock private SystemActionPerformer mMockSystemActionPerformer;
- @Mock private IBinder mMockService;
- @Mock private IAccessibilityServiceClient mMockServiceInterface;
+ @Mock private IBinder mMockClientBinder;
+ @Mock private IAccessibilityServiceClient mMockClient;
@Mock private KeyEventDispatcher mMockKeyEventDispatcher;
@Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection;
@Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
@@ -247,9 +247,9 @@ public class AbstractAccessibilityServiceConnectionTest {
mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal,
mMockSystemActionPerformer, mMockA11yWindowManager);
- // Assume that the service is connected
- mServiceConnection.mService = mMockService;
- mServiceConnection.mServiceInterface = mMockServiceInterface;
+ // Assume that the client is connected
+ mServiceConnection.mClientBinder = mMockClientBinder;
+ mServiceConnection.mClient = mMockClient;
// Update security policy for this service
when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true);
@@ -273,7 +273,7 @@ public class AbstractAccessibilityServiceConnectionTest {
final KeyEvent mockKeyEvent = mock(KeyEvent.class);
mServiceConnection.onKeyEvent(mockKeyEvent, sequenceNumber);
- verify(mMockServiceInterface).onKeyEvent(mockKeyEvent, sequenceNumber);
+ verify(mMockClient).onKeyEvent(mockKeyEvent, sequenceNumber);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 566feb7e3d80..a2965b3c51f1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -33,11 +33,11 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
-import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static com.google.common.truth.Truth.assertThat;
@@ -63,8 +63,11 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.NonNull;
import android.app.PendingIntent;
import android.app.RemoteAction;
+import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -77,6 +80,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.input.KeyGestureEvent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -134,10 +138,12 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.internal.util.reflection.FieldReader;
@@ -212,6 +218,7 @@ public class AccessibilityManagerServiceTest {
@Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
@Mock private ProxyManager mProxyManager;
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+ @Mock private DevicePolicyManager mDevicePolicyManager;
@Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
@Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private IAccessibilityManager mA11yManagerServiceOnDevice;
@@ -241,6 +248,7 @@ public class AccessibilityManagerServiceTest {
UserManagerInternal.class, mMockUserManagerInternal);
LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
mInputFilter = mock(FakeInputFilter.class);
+ mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
mMockMagnificationConnectionManager);
@@ -253,6 +261,11 @@ public class AccessibilityManagerServiceTest {
mMockA11yController);
when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt())).thenReturn(true);
+ when(mMockSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(anyInt()))
+ .then(AdditionalAnswers.returnsFirstArg());
+ when(mMockSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+ eq(UserHandle.USER_CURRENT)))
+ .thenReturn(mTestableContext.getUserId());
final ArrayList<Display> displays = new ArrayList<>();
final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(),
@@ -276,14 +289,21 @@ public class AccessibilityManagerServiceTest {
mInputFilter,
mProxyManager,
mFakePermissionEnforcer);
+ mA11yms.switchUser(mTestableContext.getUserId());
+ mTestableLooper.processAllMessages();
+ FieldSetter.setField(mA11yms,
+ AccessibilityManagerService.class.getDeclaredField("mHasInputFilter"), true);
+ FieldSetter.setField(mA11yms,
+ AccessibilityManagerService.class.getDeclaredField("mInputFilter"), mInputFilter);
final AccessibilityUserState userState = new AccessibilityUserState(
- mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
- mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+ mTestableContext.getUserId(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
mA11yManagerServiceOnDevice = (IAccessibilityManager) new FieldReader(am,
AccessibilityManager.class.getDeclaredField("mService")).read();
FieldSetter.setField(am, AccessibilityManager.class.getDeclaredField("mService"), mA11yms);
+ Mockito.clearInvocations(mMockMagnificationConnectionManager);
}
@After
@@ -594,7 +614,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
throws RemoteException {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
@@ -647,7 +666,6 @@ public class AccessibilityManagerServiceTest {
mA11yms.getCurrentUserIdLocked());
userState.setMagnificationCapabilitiesLocked(
ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
- //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
// Invokes client change to trigger onUserStateChanged.
@@ -1020,6 +1038,7 @@ public class AccessibilityManagerServiceTest {
when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+ mTestableLooper.processAllMessages();
assertThat(lockState.get()).containsExactly(false);
}
@@ -1109,9 +1128,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1129,11 +1145,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING)
public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
@@ -1161,9 +1173,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
@@ -1181,9 +1190,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
@@ -1227,9 +1233,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
@@ -1250,9 +1253,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
mA11yms.enableShortcutsForTargets(
@@ -1292,9 +1292,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
AccessibilityUtils.setAccessibilityServiceState(
mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1315,9 +1312,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
@@ -1337,9 +1331,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1358,9 +1349,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
@@ -1380,9 +1368,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1402,9 +1387,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
@@ -1424,9 +1406,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1446,9 +1425,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
@@ -1474,9 +1450,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableQuickSettings_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1610,7 +1583,8 @@ public class AccessibilityManagerServiceTest {
List.of(tile)
);
- assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+ assertThat(mA11yms.getCurrentUserState()
+ .getShortcutTargetsLocked(QUICK_SETTINGS)).doesNotContain(tile.flattenToString());
}
@Test
@@ -1631,7 +1605,7 @@ public class AccessibilityManagerServiceTest {
List.of(tile)
);
- assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+ assertThat(mA11yms.getCurrentUserState().getShortcutTargetsLocked(QUICK_SETTINGS))
.contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
}
@@ -1651,7 +1625,7 @@ public class AccessibilityManagerServiceTest {
);
assertThat(
- mA11yms.getCurrentUserState().getA11yQsTargets()
+ mA11yms.getCurrentUserState().getShortcutTargetsLocked(QUICK_SETTINGS)
).containsExactlyElementsIn(List.of(
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
@@ -1671,7 +1645,7 @@ public class AccessibilityManagerServiceTest {
);
assertThat(
- mA11yms.getCurrentUserState().getA11yQsTargets()
+ mA11yms.getCurrentUserState().getShortcutTargetsLocked(QUICK_SETTINGS)
).doesNotContain(
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
}
@@ -1679,14 +1653,17 @@ public class AccessibilityManagerServiceTest {
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
String colorInversionTile =
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
broadcastSettingRestored(
ShortcutUtils.convertToKey(QUICK_SETTINGS),
@@ -1702,15 +1679,18 @@ public class AccessibilityManagerServiceTest {
@Test
@DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
String colorInversionTile =
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
broadcastSettingRestored(
ShortcutUtils.convertToKey(QUICK_SETTINGS),
@@ -1724,7 +1704,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
setupShortcutTargetServices();
AccessibilityUserState userState = mA11yms.getCurrentUserState();
@@ -1735,19 +1714,18 @@ public class AccessibilityManagerServiceTest {
ComponentName::getPackageName).toList().toArray(new String[0]);
PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
- when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(monitor.getChangingUserId()).thenReturn(userState.mUserId);
mA11yms.setPackageMonitor(monitor);
assertTrue(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
packages,
- UserHandle.USER_SYSTEM,
+ userState.mUserId,
false
));
}
@Test
- @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
public void onHandleForceStop_doIt_packageEnabled_returnsFalse() {
setupShortcutTargetServices();
AccessibilityUserState userState = mA11yms.getCurrentUserState();
@@ -1758,42 +1736,43 @@ public class AccessibilityManagerServiceTest {
ComponentName::getPackageName).toList().toArray(new String[0]);
PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
- when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(monitor.getChangingUserId()).thenReturn(userState.mUserId);
mA11yms.setPackageMonitor(monitor);
assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
packages,
- UserHandle.USER_SYSTEM,
+ userState.mUserId,
true
));
}
@Test
- @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
public void onHandleForceStop_dontDoIt_packageNotEnabled_returnsFalse() {
PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
- when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(monitor.getChangingUserId()).thenReturn(mA11yms.getCurrentUserIdLocked());
mA11yms.setPackageMonitor(monitor);
assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
new String[]{"FOO", "BAR"},
- UserHandle.USER_SYSTEM,
+ mA11yms.getCurrentUserIdLocked(),
false
));
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
public void restoreShortcutTargets_hardware_targetsMerged() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
final String otherPrevious = TARGET_MAGNIFICATION;
final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE_NAME;
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
mA11yms.enableShortcutsForTargets(
true, HARDWARE, List.of(servicePrevious, otherPrevious), userState.mUserId);
@@ -1810,20 +1789,20 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
- Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
// default is present in userState & setting, so it's not cleared
- putShortcutSettingForUser(HARDWARE, serviceDefault, UserHandle.USER_SYSTEM);
+ putShortcutSettingForUser(HARDWARE, serviceDefault, userState.mUserId);
userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
broadcastSettingRestored(
@@ -1838,10 +1817,10 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
- Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
// Restored value from the broadcast contains both default and non-default service.
@@ -1849,8 +1828,8 @@ public class AccessibilityManagerServiceTest {
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
@@ -1865,10 +1844,10 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
- Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
// Restored value from the broadcast contains both default and non-default service.
@@ -1876,13 +1855,13 @@ public class AccessibilityManagerServiceTest {
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
// UserState has default, but setting is null (this emulates a typical scenario in SUW).
userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
- putShortcutSettingForUser(HARDWARE, null, UserHandle.USER_SYSTEM);
+ putShortcutSettingForUser(HARDWARE, null, userState.mUserId);
broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
/*newValue=*/combinedRestored);
@@ -1900,8 +1879,8 @@ public class AccessibilityManagerServiceTest {
public void onNavButtonNavigation_migratesGestureTargets() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(
Set.of(TARGET_STANDARD_A11Y_SERVICE_NAME), SOFTWARE);
@@ -1924,20 +1903,20 @@ public class AccessibilityManagerServiceTest {
public void onNavButtonNavigation_gestureTargets_noButtonTargets_navBarButtonMode() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(Set.of(), SOFTWARE);
userState.updateShortcutTargetsLocked(
Set.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()), GESTURE);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
mA11yms.updateShortcutsForCurrentNavigationMode();
- assertThat(ShortcutUtils.getButtonMode(mTestableContext, UserHandle.USER_SYSTEM))
+ assertThat(ShortcutUtils.getButtonMode(mTestableContext, userState.mUserId))
.isEqualTo(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
}
@@ -1946,11 +1925,11 @@ public class AccessibilityManagerServiceTest {
public void onGestureNavigation_floatingMenuMode() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -1965,8 +1944,8 @@ public class AccessibilityManagerServiceTest {
public void onNavigation_revertGestureTargets() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(
Set.of(TARGET_STANDARD_A11Y_SERVICE_NAME), SOFTWARE);
@@ -1989,8 +1968,8 @@ public class AccessibilityManagerServiceTest {
public void onNavigation_gestureNavigation_gestureButtonMode_migratesTargetsToGesture() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(Set.of(
TARGET_STANDARD_A11Y_SERVICE_NAME,
@@ -1998,7 +1977,7 @@ public class AccessibilityManagerServiceTest {
userState.updateShortcutTargetsLocked(Set.of(), GESTURE);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
mA11yms.updateShortcutsForCurrentNavigationMode();
@@ -2014,11 +1993,11 @@ public class AccessibilityManagerServiceTest {
@DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void onNavigation_gestureNavigation_correctsButtonMode() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2032,11 +2011,11 @@ public class AccessibilityManagerServiceTest {
@DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void onNavigation_navBarNavigation_correctsButtonMode() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2050,8 +2029,8 @@ public class AccessibilityManagerServiceTest {
public void showAccessibilityTargetSelection_navBarNavigationMode_softwareExtra() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2066,8 +2045,8 @@ public class AccessibilityManagerServiceTest {
public void showAccessibilityTargetSelection_gestureNavigationMode_softwareExtra() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2082,8 +2061,8 @@ public class AccessibilityManagerServiceTest {
public void showAccessibilityTargetSelection_gestureNavigationMode_gestureExtra() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2117,18 +2096,19 @@ public class AccessibilityManagerServiceTest {
public void switchUser_callsUserInitializationCompleteCallback() throws RemoteException {
mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
- mA11yms.switchUser(UserHandle.MIN_SECONDARY_USER_ID);
+ int newUserId = mA11yms.getCurrentUserIdLocked() + 1;
+ mA11yms.switchUser(newUserId);
+ mTestableLooper.processAllMessages();
- verify(mUserInitializationCompleteCallback).onUserInitializationComplete(
- UserHandle.MIN_SECONDARY_USER_ID);
+ verify(mUserInitializationCompleteCallback).onUserInitializationComplete(newUserId);
}
@Test
@DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void getShortcutTypeForGenericShortcutCalls_softwareType() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
assertThat(mA11yms.getShortcutTypeForGenericShortcutCalls(userState.mUserId))
.isEqualTo(SOFTWARE);
@@ -2138,8 +2118,8 @@ public class AccessibilityManagerServiceTest {
@EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void getShortcutTypeForGenericShortcutCalls_gestureNavigationMode_gestureType() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2151,8 +2131,8 @@ public class AccessibilityManagerServiceTest {
@EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void getShortcutTypeForGenericShortcutCalls_buttonNavigationMode_softwareType() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2160,16 +2140,196 @@ public class AccessibilityManagerServiceTest {
.isEqualTo(SOFTWARE);
}
+ @Test
+ @EnableFlags({android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED,
+ android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS})
+ public void isAccessibilityTargetAllowed_nonSystemUserId_useEcmWithNonSystemUserId() {
+ String fakePackageName = "FAKE_PACKAGE_NAME";
+ int uid = 0; // uid is not used in the actual implementation when flags are on
+ int userId = mTestableContext.getUserId() + 1234;
+ when(mDevicePolicyManager.getPermittedAccessibilityServices(userId)).thenReturn(
+ List.of(fakePackageName));
+ Context mockUserContext = mock(Context.class);
+ mTestableContext.addMockUserContext(userId, mockUserContext);
+
+ mA11yms.isAccessibilityTargetAllowed(fakePackageName, uid, userId);
+
+ verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class);
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_toggleMagnifier() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).containsExactly(MAGNIFICATION_CONTROLLER_NAME);
+
+ // The magnifier will only be toggled on the second event received since the first is
+ // used to toggle the feature on.
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ verify(mInputFilter).notifyMagnificationShortcutTriggered(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_trustedService() {
+ setupAccessibilityServiceConnection(FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo trustedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(trustedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ trustedService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{trustedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).containsExactly(
+ trustedService.getComponentName().flattenToString());
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_preinstalledService() {
+ setupAccessibilityServiceConnection(FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo untrustedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(untrustedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ untrustedService.getComponentName().flattenToString());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_downloadedService() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo downloadedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ false, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(downloadedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ downloadedService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{downloadedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_defaultNotInstalled() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo installedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ final AccessibilityServiceInfo defaultService = mockAccessibilityServiceInfo(
+ new ComponentName("package_b", "class_b"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(installedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ defaultService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{defaultService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_noDefault() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo installedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(installedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{installedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(
- setting, UserHandle.USER_SYSTEM, str -> str, result);
+ setting, mA11yms.getCurrentUserIdLocked(), str -> str, result);
return result;
}
private void writeStringsToSetting(Set<String> strings, String setting) {
mA11yms.persistColonDelimitedSetToSettingLocked(
- setting, UserHandle.USER_SYSTEM, strings, str -> str);
+ setting, mA11yms.getCurrentUserIdLocked(), strings, str -> str);
}
private void broadcastSettingRestored(String setting, String newValue) {
@@ -2274,12 +2434,17 @@ public class AccessibilityManagerServiceTest {
AccessibilityManagerService service) {
super(context, service);
}
+
+ @Override
+ void notifyMagnificationShortcutTriggered(int displayId) {
+ }
}
private static class A11yTestableContext extends TestableContext {
private final Context mMockContext;
private final Map<String, List<BroadcastReceiver>> mBroadcastReceivers = new ArrayMap<>();
+ private ArrayMap<Integer, Context> mMockUserContexts = new ArrayMap<>();
A11yTestableContext(Context base) {
super(base);
@@ -2317,15 +2482,24 @@ public class AccessibilityManagerServiceTest {
return mMockContext;
}
+ public void addMockUserContext(int userId, Context context) {
+ mMockUserContexts.put(userId, context);
+ }
+
+ @Override
+ @NonNull
+ public Context createContextAsUser(UserHandle user, int flags) {
+ if (mMockUserContexts.containsKey(user.getIdentifier())) {
+ return mMockUserContexts.get(user.getIdentifier());
+ }
+ return super.createContextAsUser(user, flags);
+ }
+
Map<String, List<BroadcastReceiver>> getBroadcastReceivers() {
return mBroadcastReceivers;
}
}
- private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
- return service.getCurrentUserIdLocked() == context.getUserId();
- }
-
private void putShortcutSettingForUser(@UserShortcutType int shortcutType,
String shortcutValue, int userId) {
Settings.Secure.putStringForUser(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 8914696d55da..d4f2dcc24af6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -448,4 +448,14 @@ public class AccessibilityServiceConnectionTest {
mConnection.binderDied();
assertThat(mConnection.getServiceInfo().flags & flag).isEqualTo(0);
}
+
+ @Test
+ public void setInputMethodEnabled_checksAccessWithProvidedImeIdAndUserId() {
+ final String imeId = "test_ime_id";
+ final int callingUserId = UserHandle.getCallingUserId();
+ mConnection.setInputMethodEnabled(imeId, true);
+
+ verify(mMockSecurityPolicy).canEnableDisableInputMethod(
+ eq(imeId), any(), eq(callingUserId));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 627b5e39a20a..cb52eef6adfe 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -29,8 +29,10 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TE
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -72,6 +74,7 @@ import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
@@ -172,6 +175,7 @@ public class AccessibilityUserStateTest {
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), SOFTWARE);
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), GESTURE);
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), QUICK_SETTINGS);
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), KEY_GESTURE);
mUserState.updateA11yTilesInQsPanelLocked(
Set.of(AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME));
mUserState.setTargetAssignedToAccessibilityButton(componentNameString);
@@ -199,6 +203,7 @@ public class AccessibilityUserStateTest {
assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS).isEmpty());
+ assertTrue(mUserState.getShortcutTargetsLocked(KEY_GESTURE).isEmpty());
assertTrue(mUserState.getA11yQsTilesInQsPanel().isEmpty());
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
@@ -454,17 +459,7 @@ public class AccessibilityUserStateTest {
mUserState.updateShortcutTargetsLocked(newTargets, QUICK_SETTINGS);
- assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
- }
-
- @Test
- public void getA11yQsTargets_returnsCopiedData() {
- updateShortcutTargetsLocked_quickSettings_valueUpdated();
-
- Set<String> targets = mUserState.getA11yQsTargets();
- targets.clear();
-
- assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
+ assertThat(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS)).isEqualTo(newTargets);
}
@Test
@@ -539,6 +534,31 @@ public class AccessibilityUserStateTest {
assertThat(mUserState.isShortcutMagnificationEnabledLocked()).isFalse();
}
+ @Test
+ public void getShortcutTargetsLocked_returnsCorrectTargets() {
+ for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+ if (((TRIPLETAP | TWOFINGER_DOUBLETAP) & shortcutType) == shortcutType) {
+ continue;
+ }
+ Set<String> expectedSet = Set.of(ShortcutUtils.convertToKey(shortcutType));
+ mUserState.updateShortcutTargetsLocked(expectedSet, shortcutType);
+
+ assertThat(mUserState.getShortcutTargetsLocked(shortcutType))
+ .containsExactlyElementsIn(expectedSet);
+ }
+ }
+
+ @Test
+ public void getShortcutTargetsLocked_returnsCopiedData() {
+ Set<String> set = Set.of("FOO", "BAR");
+ mUserState.updateShortcutTargetsLocked(set, SOFTWARE);
+
+ Set<String> targets = mUserState.getShortcutTargetsLocked(ALL);
+ targets.clear();
+
+ assertThat(mUserState.getShortcutTargetsLocked(ALL)).isNotEmpty();
+ }
+
private int getSecureIntForUser(String key, int userId) {
return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 403930d96a12..2ae31ad618d6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -18,20 +18,24 @@ package com.android.server.accessibility;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.EventWindowIdMatcher.eventWindowId;
import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.windowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,14 +46,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
@@ -63,6 +66,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -70,7 +74,6 @@ import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -81,17 +84,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-// This test verifies deprecated codepath. Probably changing this file means
-// AccessibilityWindowManagerWithAccessibilityWindowTest also needs to be updated.
-// LINT.IfChange
-
/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
- * enabled.
- * TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest
- * after completing the flag migration.
+ * Tests for the AccessibilityWindowManager.
*/
-@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
public class AccessibilityWindowManagerTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
@@ -122,9 +117,8 @@ public class AccessibilityWindowManagerTest {
// List of window token, mapping from windowId -> window token.
private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> window info lists.
- private final SparseArray<ArrayList<WindowInfo>> mWindowInfos =
- new SparseArray<>();
+ // List of window info lists, mapping from displayId -> a11y window lists.
+ private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
// List of callback, mapping from displayId -> callback.
private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
new SparseArray<>();
@@ -134,6 +128,13 @@ public class AccessibilityWindowManagerTest {
private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ // This maps displayId -> next region offset.
+ // Touchable region must have un-occluded area so that it's exposed to a11y services.
+ // This offset can be used as left and top of new region so that top-left of each region are
+ // kept visible.
+ // It's expected to be incremented by some amount everytime the value is used.
+ private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
+
@Mock private WindowManagerInternal mMockWindowManagerInternal;
@Mock private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
@Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
@@ -144,9 +145,6 @@ public class AccessibilityWindowManagerTest {
@Mock private IBinder mMockEmbeddedToken;
@Mock private IBinder mMockInvalidToken;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -159,7 +157,7 @@ public class AccessibilityWindowManagerTest {
anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
doAnswer((invocation) -> {
- onWindowsForAccessibilityChanged(invocation.getArgument(0), false);
+ onAccessibilityWindowsChanged(invocation.getArgument(0), false);
return null;
}).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
@@ -173,7 +171,7 @@ public class AccessibilityWindowManagerTest {
// as top focused display before each testing starts.
startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
- // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
+ // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
// Resets it for mockito verify of further test case.
Mockito.reset(mMockA11yEventSender);
@@ -237,19 +235,18 @@ public class AccessibilityWindowManagerTest {
@Test
public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ final WindowInfo focusedWindowInfo =
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, focusedWindowInfo.token));
focusedWindowInfo.focused = false;
- focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1);
- focusedWindowInfo.focused = true;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
mA11yWindowManager.onTouchInteractionStart();
setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
}
@@ -273,7 +270,7 @@ public class AccessibilityWindowManagerTest {
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should not be changed.
assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should not be changed.
@@ -301,8 +298,8 @@ public class AccessibilityWindowManagerTest {
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should be changed.
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should be changed.
@@ -312,53 +309,181 @@ public class AccessibilityWindowManagerTest {
@Test
public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
- assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1,
+ assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
is(a11yWindow.getLayer()));
}
}
@Test
public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
final IBinder windowToken = mA11yWindowManager
.getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(i).getWindowInfo();
assertThat(windowToken, is(windowInfo.token));
}
}
@Test
- public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
+ public void onWindowsChanged_embeddedWindows_shouldOnlyReportHost() throws RemoteException {
+ final Rect embeddingBounds = new Rect(0, 0, 200, 100);
+
+ // The embedded window comes front of the host window.
+ final IBinder embeddedWindowLeashToken = Mockito.mock(IBinder.class);
+ final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, embeddedWindowLeashToken, USER_SYSTEM_ID);
+ final AccessibilityWindow embeddedWindow = createMockAccessibilityWindow(
+ mA11yWindowTokens.get(embeddedWindowId), Display.DEFAULT_DISPLAY);
+ setRegionForMockAccessibilityWindow(embeddedWindow, new Region(embeddingBounds));
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0, embeddedWindow);
+
+ final IBinder hostWindowLeashToken = Mockito.mock(IBinder.class);
+ final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, hostWindowLeashToken, USER_SYSTEM_ID);
+ final AccessibilityWindow hostWindow = createMockAccessibilityWindow(
+ mA11yWindowTokens.get(hostWindowId), Display.DEFAULT_DISPLAY);
+ setRegionForMockAccessibilityWindow(hostWindow, new Region(embeddingBounds));
+ mWindows.get(Display.DEFAULT_DISPLAY).set(1, hostWindow);
+
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(
+ hostWindowLeashToken, embeddedWindowLeashToken);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(embeddedWindowId))));
+ assertThat(a11yWindows.get(0), windowId(hostWindowId));
+ final Rect bounds = new Rect();
+ a11yWindows.get(0).getBoundsInScreen(bounds);
+ assertEquals(bounds, embeddingBounds);
+ }
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertNotEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ @Test
+ public void onWindowsChanged_shouldNotReportfullyOccludedWindow() {
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow, new Region(100, 100, 300, 300));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ // index 1 is focused. Let's use the next one for this test.
+ final AccessibilityWindow occludedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(2);
+ setRegionForMockAccessibilityWindow(occludedWindow, new Region(150, 150, 250, 250));
+ final int occludedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, occludedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(frontWindowId)));
+ assertThat(a11yWindows, not(hasItem(windowId(occludedWindowId))));
}
@Test
- public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
+ assertNotEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ assertEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
}
@Test
@@ -368,14 +493,10 @@ public class AccessibilityWindowManagerTest {
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
true, USER_SYSTEM_ID);
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = token.asBinder();
- windowInfo.layer = 0;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo);
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0,
+ createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(oldWindow,
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
}
@@ -383,12 +504,12 @@ public class AccessibilityWindowManagerTest {
@Test
public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
final WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
focusedWindowInfo.focused = false;
windowInfo.focused = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
.isFocused());
}
@@ -497,15 +618,18 @@ public class AccessibilityWindowManagerTest {
@Test
public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
// Updates top 2 z-order WindowInfo are whole visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
- windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1);
- windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2,
- SCREEN_WIDTH, SCREEN_HEIGHT);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(0).getId();
@@ -523,12 +647,17 @@ public class AccessibilityWindowManagerTest {
@Test
public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
// Updates z-order #1 WindowInfo is half visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
-
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -539,9 +668,17 @@ public class AccessibilityWindowManagerTest {
@Test
public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ // Note that the second window is also exposed even if region is empty because it's focused.
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -552,16 +689,21 @@ public class AccessibilityWindowManagerTest {
@Test
public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
// Updates z-order #0 WindowInfo to have two interact-able areas.
- Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+ final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(region);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow, region);
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
+ final int windowId = a11yWindows.get(1).getId();
mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
assertFalse(outBounds.getBounds().isEmpty());
@@ -572,7 +714,8 @@ public class AccessibilityWindowManagerTest {
@Test
public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
final IBinder eventWindowToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token;
+ mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, eventWindowToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -611,11 +754,11 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -641,7 +784,7 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -690,12 +833,12 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -722,7 +865,7 @@ public class AccessibilityWindowManagerTest {
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
noUse);
assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
}
@@ -751,11 +894,11 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -763,7 +906,8 @@ public class AccessibilityWindowManagerTest {
public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
throws RemoteException {
final IBinder defaultFocusWinToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, defaultFocusWinToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -808,8 +952,8 @@ public class AccessibilityWindowManagerTest {
@Test
public void getPictureInPictureWindow_shouldNotNull() {
assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
}
@@ -823,8 +967,9 @@ public class AccessibilityWindowManagerTest {
final IAccessibilityInteractionConnection mockRemoteConnection =
mA11yWindowManager.getConnectionLocked(
USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
+ true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
verify(mockRemoteConnection).notifyOutsideTouch();
@@ -942,18 +1087,14 @@ public class AccessibilityWindowManagerTest {
@Test
public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
// Removing index 0 because it's not focused, and avoids unnecessary layer change.
final int windowId =
getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- infos.remove(0);
- for (WindowInfo info : infos) {
- // Adjust layer number because it should start from 0.
- info.layer--;
- }
+ windows.remove(0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -961,27 +1102,21 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@Test
public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
-
- for (WindowInfo info : infos) {
- // Adjust layer number because new window will have 0 so that layer number in
- // A11yWindowInfo in window won't be changed.
- info.layer++;
- }
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
false, USER_SYSTEM_ID);
- addWindowInfo(infos, token, 0);
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);
+ // Adding window to the front so that other windows' layer won't change.
+ windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+ final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -989,17 +1124,17 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@Test
public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
- infos.get(0).title = "new title";
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+ windows.get(0).getWindowInfo().title = "new title";
final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -1007,7 +1142,7 @@ public class AccessibilityWindowManagerTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1017,48 +1152,47 @@ public class AccessibilityWindowManagerTest {
}
private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
+ ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
// mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
// for the test.
- int layer = 0;
for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
true, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
for (int i = 0; i < NUM_APP_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
false, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
// Sets up current focused window of display.
// Each display has its own current focused window if config_perDisplayFocusEnabled is true.
// Otherwise only default display needs to current focused window.
if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true;
+ windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
}
// Turns on windows tracking, and update window info.
mA11yWindowManager.startTrackingWindows(displayId, false);
// Puts window lists into array.
- mWindowInfos.put(displayId, windowInfosForDisplay);
+ mWindows.put(displayId, windowsForDisplay);
// Sets the default display is the top focused display and
// its current focused window is the top focused window.
if (displayId == Display.DEFAULT_DISPLAY) {
setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
}
// Invokes callback for sending window lists to A11y framework.
- onWindowsForAccessibilityChanged(displayId, FORCE_SEND);
+ onAccessibilityWindowsChanged(displayId, FORCE_SEND);
assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowInfosForDisplay.size());
+ windowsForDisplay.size());
}
private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor =
ArgumentCaptor.forClass(
- WindowManagerInternal.WindowsForAccessibilityCallback.class);
+ WindowsForAccessibilityCallback.class);
verify(mMockWindowManagerInternal)
.setWindowsForAccessibilityCallback(eq(displayId),
windowsForAccessibilityCallbacksCaptor.capture());
@@ -1106,36 +1240,28 @@ public class AccessibilityWindowManagerTest {
return windowId;
}
- private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
- windowInfo.layer = layer;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- windowInfos.add(windowInfo);
- }
-
private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindowInfos.get(displayId).get(index).token;
+ final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
return mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, windowToken);
}
private void setTopFocusedWindowAndDisplay(int displayId, int index) {
// Sets the top focus window.
- mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token;
+ mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
// Sets the top focused display.
mTopFocusedDisplayId = displayId;
}
- private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) {
+ private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
if (callbacks == null) {
callbacks = getWindowsForAccessibilityCallbacks(displayId);
mCallbackOfWindows.put(displayId, callbacks);
}
- callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, mWindowInfos.get(displayId));
+ callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
+ mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
+ mWindows.get(displayId));
}
private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
@@ -1144,23 +1270,23 @@ public class AccessibilityWindowManagerTest {
if (mSupportPerDisplayFocus) {
// Gets the old focused window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Gets the new window of display which wants to change focused window.
focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
} else {
// Gets the window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
// Gets the old focused window of old top focused display.
focusedWindowInfo =
- mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Changes the top focused display and window.
@@ -1168,6 +1294,39 @@ public class AccessibilityWindowManagerTest {
}
}
+ private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ windowInfo.token = windowToken.asBinder();
+
+ final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
+ when(window.getWindowInfo()).thenReturn(windowInfo);
+ when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
+ when(window.isTouchable()).thenReturn(true);
+ when(window.getType()).thenReturn(windowInfo.type);
+
+ setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
+ return window;
+ }
+
+ private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInScreen(any(Region.class));
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInWindow(any(Region.class));
+ }
+
+ private Region nextToucableRegion(int displayId) {
+ final int topLeft = mNextRegionOffsets.get(displayId, 0);
+ final int bottomRight = topLeft + 100;
+ mNextRegionOffsets.put(displayId, topLeft + 10);
+ return new Region(topLeft, topLeft, bottomRight, bottomRight);
+ }
+
@Nullable
private static String toString(@Nullable CharSequence cs) {
return cs == null ? null : cs.toString();
@@ -1196,16 +1355,16 @@ public class AccessibilityWindowManagerTest {
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1241,5 +1400,27 @@ public class AccessibilityWindowManagerTest {
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
-// LINT.ThenChange(/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
deleted file mode 100644
index 19041451c8eb..000000000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ /dev/null
@@ -1,1444 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.accessibility;
-
-import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.LocaleList;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.IWindow;
-import android.view.WindowInfo;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowAttributes;
-import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.accessibility.IAccessibilityInteractionConnection;
-
-import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
-import com.android.server.accessibility.test.MessageCapturingHandler;
-import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
-import com.android.server.wm.WindowManagerInternal;
-import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
-
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
- * TODO(b/322444245): Merge with AccessibilityWindowManagerTest
- * after completing the flag migration.
- */
-@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
-public class AccessibilityWindowManagerWithAccessibilityWindowTest {
- private static final String PACKAGE_NAME = "com.android.server.accessibility";
- private static final boolean FORCE_SEND = true;
- private static final boolean SEND_ON_WINDOW_CHANGES = false;
- private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM;
- private static final int USER_PROFILE = 11;
- private static final int USER_PROFILE_PARENT = 1;
- private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
- private static final int NUM_GLOBAL_WINDOWS = 4;
- private static final int NUM_APP_WINDOWS = 4;
- private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS);
- private static final int DEFAULT_FOCUSED_INDEX = 1;
- private static final int SCREEN_WIDTH = 1080;
- private static final int SCREEN_HEIGHT = 1920;
- private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
- private static final int HOST_WINDOW_ID = 10;
- private static final int EMBEDDED_WINDOW_ID = 11;
- private static final int OTHER_WINDOW_ID = 12;
-
- private AccessibilityWindowManager mA11yWindowManager;
- // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true,
- // i.e., each display would have its current focused window, and one of all focused windows
- // would be top focused window. Otherwise, window manager only supports one focused window
- // at all displays, and that focused window would be top focused window.
- private boolean mSupportPerDisplayFocus = false;
- private int mTopFocusedDisplayId = Display.INVALID_DISPLAY;
- private IBinder mTopFocusedWindowToken = null;
-
- // List of window token, mapping from windowId -> window token.
- private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> a11y window lists.
- private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
- // List of callback, mapping from displayId -> callback.
- private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
- new SparseArray<>();
- // List of display ID.
- private final ArrayList<Integer> mExpectedDisplayList = new ArrayList<>(Arrays.asList(
- Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID));
-
- private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
-
- // This maps displayId -> next region offset.
- // Touchable region must have un-occluded area so that it's exposed to a11y services.
- // This offset can be used as left and top of new region so that top-left of each region are
- // kept visible.
- // It's expected to be incremented by some amount everytime the value is used.
- private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
-
- @Mock
- private WindowManagerInternal mMockWindowManagerInternal;
- @Mock
- private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
- @Mock
- private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
- @Mock
- private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
- @Mock
- private AccessibilityTraceManager mMockA11yTraceManager;
-
- @Mock
- private IBinder mMockHostToken;
- @Mock
- private IBinder mMockEmbeddedToken;
- @Mock
- private IBinder mMockInvalidToken;
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Before
- public void setUp() throws RemoteException {
- MockitoAnnotations.initMocks(this);
- when(mMockA11yUserManager.getCurrentUserIdLocked()).thenReturn(USER_SYSTEM_ID);
- when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
- USER_PROFILE)).thenReturn(USER_PROFILE_PARENT);
- when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
- USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID);
- when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked(
- anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
-
- doAnswer((invocation) -> {
- onAccessibilityWindowsChanged(invocation.getArgument(0), false);
- return null;
- }).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
-
- mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler,
- mMockWindowManagerInternal,
- mMockA11yEventSender,
- mMockA11ySecurityPolicy,
- mMockA11yUserManager,
- mMockA11yTraceManager);
- // Starts tracking window of default display and sets the default display
- // as top focused display before each testing starts.
- startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
-
- // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
- // Resets it for mockito verify of further test case.
- Mockito.reset(mMockA11yEventSender);
-
- registerLeashedTokenAndWindowId();
- }
-
- @After
- public void tearDown() {
- mHandler.removeAllMessages();
- }
-
- @Test
- public void startTrackingWindows_shouldEnableWindowManagerCallback() {
- // AccessibilityWindowManager#startTrackingWindows already invoked in setup.
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- final WindowsForAccessibilityCallback callbacks =
- mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
- verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
- eq(Display.DEFAULT_DISPLAY), eq(callbacks));
- }
-
- @Test
- public void stopTrackingWindows_shouldDisableWindowManagerCallback() {
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- Mockito.reset(mMockWindowManagerInternal);
-
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- assertFalse(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
- eq(Display.DEFAULT_DISPLAY), isNull());
-
- }
-
- @Test
- public void stopTrackingWindows_shouldClearWindows() {
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
-
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- assertNull(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY));
- assertEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
- assertEquals(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID),
- activeWindowId);
- }
-
- @Test
- public void stopTrackingWindows_onNonTopFocusedDisplay_shouldNotResetTopFocusWindow()
- throws RemoteException {
- // At setup, the default display sets be the top focused display and
- // its current focused window sets be the top focused window.
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
- // Stops tracking windows of second display.
- mA11yWindowManager.stopTrackingWindows(SECONDARY_DISPLAY_ID);
- assertNotEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
- }
-
- @Test
- public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- final WindowInfo focusedWindowInfo =
- mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
- assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, focusedWindowInfo.token));
-
- focusedWindowInfo.focused = false;
- mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
-
- mA11yWindowManager.onTouchInteractionStart();
- setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
- }
-
- @Test
- public void
- onWindowsChanged_focusChangeOnNonTopFocusedDisplay_perDisplayFocusOn_notChangeWindow()
- throws RemoteException {
- // At setup, the default display sets be the top focused display and
- // its current focused window sets be the top focused window.
- // Sets supporting multiple focused window, i.e., config_perDisplayFocusEnabled is true.
- mSupportPerDisplayFocus = true;
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- // Gets the active window.
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- // Gets the top focused window.
- final int topFocusedWindowId =
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
- // Changes the current focused window at second display.
- changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
- DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
-
- onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
- // The active window should not be changed.
- assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
- // The top focused window should not be changed.
- assertEquals(topFocusedWindowId,
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
- }
-
- @Test
- public void
- onWindowChange_focusChangeToNonTopFocusedDisplay_perDisplayFocusOff_shouldChangeWindow()
- throws RemoteException {
- // At setup, the default display sets be the top focused display and
- // its current focused window sets be the top focused window.
- // Sets not supporting multiple focused window, i.e., config_perDisplayFocusEnabled is
- // false.
- mSupportPerDisplayFocus = false;
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- // Gets the active window.
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- // Gets the top focused window.
- final int topFocusedWindowId =
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
- // Changes the current focused window from default display to second display.
- changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
- DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
- // The active window should be changed.
- assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
- // The top focused window should be changed.
- assertNotEquals(topFocusedWindowId,
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
- }
-
- @Test
- public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
- List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < a11yWindows.size(); i++) {
- final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
- is(a11yWindow.getLayer()));
- }
- }
-
- @Test
- public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
- List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < a11yWindows.size(); i++) {
- final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final IBinder windowToken = mA11yWindowManager
- .getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
- .get(i).getWindowInfo();
- assertThat(windowToken, is(windowInfo.token));
- }
- }
-
- @Test
- public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- when(window.isTouchable()).thenReturn(false);
- final int windowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, window.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, not(hasItem(windowId(windowId))));
- }
-
- @Test
- public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX);
- when(window.isTouchable()).thenReturn(false);
- final int windowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, window.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasItem(windowId(windowId)));
- }
-
- @Test
- public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
- // Make the focused trusted un-touchable window fullscreen.
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX);
- setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- when(window.isTouchable()).thenReturn(false);
- when(window.isTrustedOverlay()).thenReturn(true);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
- }
-
- @Test
- public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
- // Make the a11y overlay window fullscreen.
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
- }
-
- @Test
- public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
- // Make the front window fullscreen.
- final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(frontWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
-
- final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX);
- final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- assertThat(a11yWindows.get(0), windowId(frontWindowId));
- assertThat(a11yWindows.get(1), windowId(focusedWindowId));
- }
-
- @Test
- public void onWindowsChanged_embeddedWindows_shouldOnlyReportHost() throws RemoteException {
- final Rect embeddingBounds = new Rect(0, 0, 200, 100);
-
- // The embedded window comes front of the host window.
- final IBinder embeddedWindowLeashToken = Mockito.mock(IBinder.class);
- final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, embeddedWindowLeashToken, USER_SYSTEM_ID);
- final AccessibilityWindow embeddedWindow = createMockAccessibilityWindow(
- mA11yWindowTokens.get(embeddedWindowId), Display.DEFAULT_DISPLAY);
- setRegionForMockAccessibilityWindow(embeddedWindow, new Region(embeddingBounds));
- mWindows.get(Display.DEFAULT_DISPLAY).set(0, embeddedWindow);
-
- final IBinder hostWindowLeashToken = Mockito.mock(IBinder.class);
- final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, hostWindowLeashToken, USER_SYSTEM_ID);
- final AccessibilityWindow hostWindow = createMockAccessibilityWindow(
- mA11yWindowTokens.get(hostWindowId), Display.DEFAULT_DISPLAY);
- setRegionForMockAccessibilityWindow(hostWindow, new Region(embeddingBounds));
- mWindows.get(Display.DEFAULT_DISPLAY).set(1, hostWindow);
-
- mA11yWindowManager.associateEmbeddedHierarchyLocked(
- hostWindowLeashToken, embeddedWindowLeashToken);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, not(hasItem(windowId(embeddedWindowId))));
- assertThat(a11yWindows.get(0), windowId(hostWindowId));
- final Rect bounds = new Rect();
- a11yWindows.get(0).getBoundsInScreen(bounds);
- assertEquals(bounds, embeddingBounds);
- }
-
- @Test
- public void onWindowsChanged_shouldNotReportfullyOccludedWindow() {
- final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(frontWindow, new Region(100, 100, 300, 300));
- final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
-
- // index 1 is focused. Let's use the next one for this test.
- final AccessibilityWindow occludedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(2);
- setRegionForMockAccessibilityWindow(occludedWindow, new Region(150, 150, 250, 250));
- final int occludedWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, occludedWindow.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasItem(windowId(frontWindowId)));
- assertThat(a11yWindows, not(hasItem(windowId(occludedWindowId))));
- }
-
- @Test
- public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- assertNotEquals("new title",
- toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
- .get(0).getTitle()));
-
- mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertEquals("new title",
- toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
- .get(0).getTitle()));
- }
-
- @Test
- public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows()
- throws RemoteException {
- final AccessibilityWindowInfo oldWindow =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- true, USER_SYSTEM_ID);
- mWindows.get(Display.DEFAULT_DISPLAY).set(0,
- createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertNotEquals(oldWindow,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
- }
-
- @Test
- public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
- final WindowInfo focusedWindowInfo =
- mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
- final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
- focusedWindowInfo.focused = false;
- windowInfo.focused = true;
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
- .isFocused());
- }
-
- @Test
- public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() {
- for (int i = 0; i < NUM_OF_WINDOWS; i++) {
- final int windowId = mA11yWindowTokens.keyAt(i);
- final IWindow windowToken = mA11yWindowTokens.valueAt(i);
- assertNotNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
-
- mA11yWindowManager.removeAccessibilityInteractionConnection(windowToken);
- assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
- }
- }
-
- @Test
- public void remoteAccessibilityConnection_binderDied_shouldRemoveConnection() {
- for (int i = 0; i < NUM_OF_WINDOWS; i++) {
- final int windowId = mA11yWindowTokens.keyAt(i);
- final RemoteAccessibilityConnection remoteA11yConnection =
- mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId);
- assertNotNull(remoteA11yConnection);
-
- remoteA11yConnection.binderDied();
- assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
- }
- }
-
- @Test
- public void getWindowTokenForUserAndWindowId_shouldNotNull() {
- final List<AccessibilityWindowInfo> windows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < windows.size(); i++) {
- final int windowId = windows.get(i).getId();
-
- assertNotNull(mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
- USER_SYSTEM_ID, windowId));
- }
- }
-
- @Test
- public void findWindowId() {
- final List<AccessibilityWindowInfo> windows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < windows.size(); i++) {
- final int windowId = windows.get(i).getId();
- final IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
- USER_SYSTEM_ID, windowId);
-
- assertEquals(mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, windowToken), windowId);
- }
- }
-
- @Test
- public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId()
- throws RemoteException {
- final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false,
- Mockito.mock(IBinder.class), USER_SYSTEM_ID);
- assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
- }
-
- @Test
- public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() {
- final int windowId = -1;
- assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
- }
-
- @Test
- public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId()
- throws RemoteException {
- final IBinder mockHostToken = Mockito.mock(IBinder.class);
- final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
- final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockHostToken, USER_SYSTEM_ID);
- final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockEmbeddedToken, USER_SYSTEM_ID);
-
- mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
-
- final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
- embeddedWindowId);
- assertEquals(hostWindowId, resolvedWindowId);
- }
-
- @Test
- public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId()
- throws RemoteException {
- final IBinder mockHostToken = Mockito.mock(IBinder.class);
- final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
- final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockHostToken, USER_SYSTEM_ID);
- final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockEmbeddedToken, USER_SYSTEM_ID);
-
- mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
- mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken);
-
- final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
- embeddedWindowId);
- assertNotEquals(hostWindowId, resolvedWindowId);
- assertEquals(embeddedWindowId, resolvedWindowId);
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
- // Updates top 2 z-order WindowInfo are whole visible.
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
- final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
- setRegionForMockAccessibilityWindow(secondWindow,
- new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- int windowId = a11yWindows.get(0).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
-
- windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
- // Updates z-order #1 WindowInfo is half visible.
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
- final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
- setRegionForMockAccessibilityWindow(secondWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- // Note that the second window is also exposed even if region is empty because it's focused.
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertTrue(outBounds.getBounds().isEmpty());
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
- // Updates z-order #0 WindowInfo to have two interact-able areas.
- final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
- region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow, region);
- final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
- setRegionForMockAccessibilityWindow(secondWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- final int windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertFalse(outBounds.getBounds().isEmpty());
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT - 400));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
- final IBinder eventWindowToken =
- mWindows.get(Display.DEFAULT_DISPLAY)
- .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
- final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, eventWindowToken);
- when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
- .thenReturn(eventWindowToken);
-
- final int noUse = 0;
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- noUse,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
- assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- is(eventWindowId));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_hoverEvent_touchInteract_shouldSetActiveWindow() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX + 1);
- final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- assertThat(currentActiveWindowId, is(not(eventWindowId)));
-
- final int noUse = 0;
- mA11yWindowManager.onTouchInteractionStart();
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- noUse,
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(2))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(currentActiveWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- assertThat(captor.getAllValues().get(1),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(eventWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_shouldUpdateA11yFocus() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX);
- final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
- assertThat(currentA11yFocusedWindowId, is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
-
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(eventWindowId),
- a11yWindowChanges(
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_defaultToSecondary()
- throws RemoteException {
- runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
- Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_SecondaryToDefault()
- throws RemoteException {
- runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
- SECONDARY_DISPLAY_ID, Display.DEFAULT_DISPLAY);
- }
-
- private void runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
- int initialDisplayId, int eventDisplayId) throws RemoteException {
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- final int initialWindowId = getWindowIdFromWindowInfosForDisplay(
- initialDisplayId, DEFAULT_FOCUSED_INDEX);
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- initialWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(initialWindowId));
- Mockito.reset(mMockA11yEventSender);
-
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(
- eventDisplayId, DEFAULT_FOCUSED_INDEX);
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(2))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(initialDisplayId),
- eventWindowId(initialWindowId),
- a11yWindowChanges(
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
- assertThat(captor.getAllValues().get(1),
- allOf(displayId(eventDisplayId),
- eventWindowId(eventWindowId),
- a11yWindowChanges(
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_clearA11yFocusEvent_shouldClearA11yFocus() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX);
- final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
- assertThat(currentA11yFocusedWindowId, is(not(eventWindowId)));
-
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
- is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
- }
-
- @Test
- public void onTouchInteractionEnd_shouldRollbackActiveWindow() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX + 1);
- final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- assertThat(currentActiveWindowId, is(not(eventWindowId)));
-
- final int noUse = 0;
- mA11yWindowManager.onTouchInteractionStart();
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- noUse,
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
- // AccessibilityEventSender is invoked after active window changed. Reset it.
- Mockito.reset(mMockA11yEventSender);
-
- mA11yWindowManager.onTouchInteractionEnd();
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(2))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(eventWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- assertThat(captor.getAllValues().get(1),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(currentActiveWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- }
-
- @Test
- public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
- throws RemoteException {
- final IBinder defaultFocusWinToken =
- mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
- final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, defaultFocusWinToken);
- when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
- .thenReturn(defaultFocusWinToken);
- final int newFocusWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX + 1);
- final IAccessibilityInteractionConnection mockNewFocusConnection =
- mA11yWindowManager.getConnectionLocked(
- USER_SYSTEM_ID, newFocusWindowId).getRemote();
-
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- defaultFocusWindowId,
- noUse,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(defaultFocusWindowId));
- assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- is(defaultFocusWindowId));
-
- mA11yWindowManager.onTouchInteractionStart();
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- newFocusWindowId,
- noUse,
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- noUse);
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- newFocusWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(newFocusWindowId));
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(newFocusWindowId));
-
- mA11yWindowManager.onTouchInteractionEnd();
- mHandler.sendLastMessage();
- verify(mockNewFocusConnection).clearAccessibilityFocus();
- }
-
- @Test
- public void getPictureInPictureWindow_shouldNotNull() {
- assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- }
-
- @Test
- public void notifyOutsideTouch() throws RemoteException {
- final int targetWindowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 1);
- final int outsideWindowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- final IAccessibilityInteractionConnection mockRemoteConnection =
- mA11yWindowManager.getConnectionLocked(
- USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
- true;
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
- verify(mockRemoteConnection).notifyOutsideTouch();
- }
-
- @Test
- public void addAccessibilityInteractionConnection_profileUser_findInParentUser()
- throws RemoteException {
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, USER_PROFILE);
- final int windowId = mA11yWindowManager.findWindowIdLocked(
- USER_PROFILE_PARENT, token.asBinder());
- assertTrue(windowId >= 0);
- }
-
- @Test
- public void getDisplayList() throws RemoteException {
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
-
- final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
- DISPLAY_TYPE_DEFAULT);
- assertTrue(displayList.equals(mExpectedDisplayList));
- }
-
- @Test
- public void setAccessibilityWindowIdToSurfaceMetadata()
- throws RemoteException {
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- true, USER_SYSTEM_ID);
- int windowId = -1;
- for (int i = 0; i < mA11yWindowTokens.size(); i++) {
- if (mA11yWindowTokens.valueAt(i).equals(token)) {
- windowId = mA11yWindowTokens.keyAt(i);
- }
- }
- assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId);
- verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
- token.asBinder(), windowId);
-
- mA11yWindowManager.removeAccessibilityInteractionConnection(token);
- verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
- token.asBinder(), -1);
- }
-
- @Test
- public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() {
- mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
- assertEquals(hostToken, mMockHostToken);
- }
-
- @Test
- public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() {
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
- assertNull(hostToken);
- }
-
- @Test
- public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() {
- mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
- mA11yWindowManager.disassociateLocked(mMockEmbeddedToken);
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
- assertNull(hostToken);
- }
-
- @Test
- public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() {
- mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
- mA11yWindowManager.disassociateLocked(mMockHostToken);
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken);
- assertNull(hostToken);
- }
-
- @Test
- public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() {
- final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken);
- assertEquals(windowId, HOST_WINDOW_ID);
- }
-
- @Test
- public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() {
- final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken);
- assertEquals(windowId, INVALID_ID);
- }
-
- @Test
- public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
- final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
- assertEquals(token, mMockHostToken);
- }
-
- @Test
- public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
- final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
- assertNull(token);
- }
-
- @Test
- public void setAccessibilityWindowAttributes_windowIsNotRegistered_titleIsChanged() {
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
- layoutParams.accessibilityTitle = "accessibility window title";
- final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
- layoutParams, new LocaleList());
-
- mA11yWindowManager.setAccessibilityWindowAttributes(Display.DEFAULT_DISPLAY, windowId,
- USER_SYSTEM_ID, attributes);
-
- final AccessibilityWindowInfo a11yWindow = mA11yWindowManager.findA11yWindowInfoByIdLocked(
- windowId);
- assertEquals(toString(layoutParams.accessibilityTitle), toString(a11yWindow.getTitle()));
- }
-
- @Test
- public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
-
- // Removing index 0 because it's not focused, and avoids unnecessary layer change.
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- windows.remove(0);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
-
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(windowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
- }
-
- @Test
- public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
-
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, USER_SYSTEM_ID);
- // Adding window to the front so that other windows' layer won't change.
- windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
-
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(windowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
- }
-
- @Test
- public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
- windows.get(0).getWindowInfo().title = "new title";
- final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
-
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(windowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
- }
-
- private void registerLeashedTokenAndWindowId() {
- mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
- mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
- }
-
- private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
- // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
- // mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
- // for the test.
- for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
- final IWindow token = addAccessibilityInteractionConnection(displayId,
- true, USER_SYSTEM_ID);
- windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
-
- }
- for (int i = 0; i < NUM_APP_WINDOWS; i++) {
- final IWindow token = addAccessibilityInteractionConnection(displayId,
- false, USER_SYSTEM_ID);
- windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
- }
- // Sets up current focused window of display.
- // Each display has its own current focused window if config_perDisplayFocusEnabled is true.
- // Otherwise only default display needs to current focused window.
- if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
- }
- // Turns on windows tracking, and update window info.
- mA11yWindowManager.startTrackingWindows(displayId, false);
- // Puts window lists into array.
- mWindows.put(displayId, windowsForDisplay);
- // Sets the default display is the top focused display and
- // its current focused window is the top focused window.
- if (displayId == Display.DEFAULT_DISPLAY) {
- setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
- }
- // Invokes callback for sending window lists to A11y framework.
- onAccessibilityWindowsChanged(displayId, FORCE_SEND);
-
- assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowsForDisplay.size());
- }
-
- private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
- ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor =
- ArgumentCaptor.forClass(
- WindowsForAccessibilityCallback.class);
- verify(mMockWindowManagerInternal)
- .setWindowsForAccessibilityCallback(eq(displayId),
- windowsForAccessibilityCallbacksCaptor.capture());
- return windowsForAccessibilityCallbacksCaptor.getValue();
- }
-
- private IWindow addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
- int userId) throws RemoteException {
- final IWindow mockWindowToken = Mockito.mock(IWindow.class);
- final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
- IAccessibilityInteractionConnection.class);
- final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
- final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
- final IBinder mockLeashToken = Mockito.mock(IBinder.class);
- when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
- when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
- when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
- .thenReturn(bGlobal);
- when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
- .thenReturn(displayId);
-
- int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
- mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId);
- mA11yWindowTokens.put(windowId, mockWindowToken);
- return mockWindowToken;
- }
-
- private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
- IBinder leashToken, int userId) throws RemoteException {
- final IWindow mockWindowToken = Mockito.mock(IWindow.class);
- final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
- IAccessibilityInteractionConnection.class);
- final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
- final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
- when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
- when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
- when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
- .thenReturn(bGlobal);
- when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
- .thenReturn(displayId);
-
- int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
- mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId);
- mA11yWindowTokens.put(windowId, mockWindowToken);
- return windowId;
- }
-
- private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
- return mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, windowToken);
- }
-
- private void setTopFocusedWindowAndDisplay(int displayId, int index) {
- // Sets the top focus window.
- mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
- // Sets the top focused display.
- mTopFocusedDisplayId = displayId;
- }
-
- private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
- WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
- if (callbacks == null) {
- callbacks = getWindowsForAccessibilityCallbacks(displayId);
- mCallbackOfWindows.put(displayId, callbacks);
- }
- callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
- mWindows.get(displayId));
- }
-
- private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
- int changeFocusedDisplayId, int newFocusedWindowIndex, int oldTopFocusedDisplayId,
- int oldFocusedWindowIndex) {
- if (mSupportPerDisplayFocus) {
- // Gets the old focused window of display which wants to change focused window.
- WindowInfo focusedWindowInfo =
- mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
- // Resets the focus of old focused window.
- focusedWindowInfo.focused = false;
- // Gets the new window of display which wants to change focused window.
- focusedWindowInfo =
- mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
- // Sets the focus of new focused window.
- focusedWindowInfo.focused = true;
- } else {
- // Gets the window of display which wants to change focused window.
- WindowInfo focusedWindowInfo =
- mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
- // Sets the focus of new focused window.
- focusedWindowInfo.focused = true;
- // Gets the old focused window of old top focused display.
- focusedWindowInfo =
- mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
- // Resets the focus of old focused window.
- focusedWindowInfo.focused = false;
- // Changes the top focused display and window.
- setTopFocusedWindowAndDisplay(changeFocusedDisplayId, newFocusedWindowIndex);
- }
- }
-
- private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
-
- final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
- when(window.getWindowInfo()).thenReturn(windowInfo);
- when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
- when(window.isTouchable()).thenReturn(true);
- when(window.getType()).thenReturn(windowInfo.type);
-
- setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
- return window;
- }
-
- private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
- doAnswer(invocation -> {
- ((Region) invocation.getArgument(0)).set(region);
- return null;
- }).when(window).getTouchableRegionInScreen(any(Region.class));
- doAnswer(invocation -> {
- ((Region) invocation.getArgument(0)).set(region);
- return null;
- }).when(window).getTouchableRegionInWindow(any(Region.class));
- }
-
- private Region nextToucableRegion(int displayId) {
- final int topLeft = mNextRegionOffsets.get(displayId, 0);
- final int bottomRight = topLeft + 100;
- mNextRegionOffsets.put(displayId, topLeft + 10);
- return new Region(topLeft, topLeft, bottomRight, bottomRight);
- }
-
- @Nullable
- private static String toString(@Nullable CharSequence cs) {
- return cs == null ? null : cs.toString();
- }
-
- static class DisplayIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
- private final int mDisplayId;
-
- DisplayIdMatcher(int displayId) {
- super();
- mDisplayId = displayId;
- }
-
- static DisplayIdMatcher displayId(int displayId) {
- return new DisplayIdMatcher(displayId);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityEvent event) {
- return event.getDisplayId() == mDisplayId;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to displayId " + mDisplayId);
- }
- }
-
- static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
- private int mWindowId;
-
- EventWindowIdMatcher(int windowId) {
- super();
- mWindowId = windowId;
- }
-
- static EventWindowIdMatcher eventWindowId(int windowId) {
- return new EventWindowIdMatcher(windowId);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityEvent event) {
- return event.getWindowId() == mWindowId;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to windowId " + mWindowId);
- }
- }
-
- static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
- private int mWindowChanges;
-
- WindowChangesMatcher(int windowChanges) {
- super();
- mWindowChanges = windowChanges;
- }
-
- static WindowChangesMatcher a11yWindowChanges(int windowChanges) {
- return new WindowChangesMatcher(windowChanges);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityEvent event) {
- return event.getWindowChanges() == mWindowChanges;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to window changes " + mWindowChanges);
- }
- }
-
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
- private final int mWindowId;
-
- WindowIdMatcher(int windowId) {
- super();
- mWindowId = windowId;
- }
-
- static WindowIdMatcher windowId(int windowId) {
- return new WindowIdMatcher(windowId);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityWindowInfo window) {
- return window.getId() == mWindowId;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to windowId " + mWindowId);
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
index 0988eeab5913..a55346caeeb1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
@@ -430,7 +430,7 @@ public class FlashNotificationsControllerTest {
AudioPlaybackConfiguration config = new AudioPlaybackConfiguration(
mock(PlayerBase.PlayerIdCard.class), 0, 0, 0);
config.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_STARTED,
- AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
+ AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID);
AudioAttributes.Builder builder = new AudioAttributes.Builder();
builder.setUsage(AudioAttributes.USAGE_ALARM);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 7829fcc4b44d..8df18a8c178b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -480,7 +480,7 @@ public class MagnificationProcessorTest {
if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
mFullScreenMagnificationControllerStub.resetAndStubMethods();
mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
- config.getCenterX(), config.getCenterY(), false, SERVICE_ID);
+ config.getCenterX(), config.getCenterY(), true, false, SERVICE_ID);
mMagnificationManagerStub.deactivateIfNeed();
} else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
mMagnificationManagerStub.resetAndStubMethods();
@@ -531,6 +531,9 @@ public class MagnificationProcessorTest {
};
doAnswer(enableMagnificationStubAnswer).when(
mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
+ anyFloat(), anyFloat(), anyBoolean(), anyBoolean(), eq(SERVICE_ID));
+ doAnswer(enableMagnificationStubAnswer).when(
+ mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
anyFloat(), anyFloat(), anyBoolean(), eq(SERVICE_ID));
Answer disableMagnificationStubAnswer = invocation -> {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index 019ccf93fa11..5134737ae660 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -61,7 +61,6 @@ class MouseKeysInterceptorTest {
companion object {
const val DISPLAY_ID = 1
const val DEVICE_ID = 123
- const val MOUSE_POINTER_MOVEMENT_STEP = 1.8f
// This delay is required for key events to be sent and handled correctly.
// The handler only performs a move/scroll event if it receives the key event
// at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
@@ -159,8 +158,8 @@ class MouseKeysInterceptorTest {
testLooper.dispatchAll()
// Verify the sendRelativeEvent method is called once and capture the arguments
- verifyRelativeEvents(arrayOf(-MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
- arrayOf(MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
+ verifyRelativeEvents(arrayOf(-MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
+ arrayOf(MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
}
@Test
@@ -232,7 +231,29 @@ class MouseKeysInterceptorTest {
testLooper.dispatchAll()
// Verify the sendScrollEvent method is called once and capture the arguments
- verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+ verifyScrollEvents(arrayOf<Float>(0f),
+ arrayOf<Float>(MouseKeysInterceptor.MOUSE_SCROLL_STEP))
+ }
+
+ @Test
+ fun whenScrollToggleOn_ScrollRightKeyIsPressed_scrollEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCodeScrollToggle = MouseKeysInterceptor.MouseKeyEvent.SCROLL_TOGGLE.keyCodeValue
+ val keyCodeScroll = MouseKeysInterceptor.MouseKeyEvent.RIGHT_MOVE_OR_SCROLL.keyCodeValue
+
+ val scrollToggleDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeScrollToggle, 0, 0, DEVICE_ID, 0)
+ val scrollDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeScroll, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(scrollToggleDownEvent, 0)
+ mouseKeysInterceptor.onKeyEvent(scrollDownEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendScrollEvent method is called once and capture the arguments
+ verifyScrollEvents(arrayOf<Float>(-MouseKeysInterceptor.MOUSE_SCROLL_STEP),
+ arrayOf<Float>(0f))
}
@Test
@@ -247,7 +268,8 @@ class MouseKeysInterceptorTest {
testLooper.dispatchAll()
// Verify the sendRelativeEvent method is called once and capture the arguments
- verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(-MOUSE_POINTER_MOVEMENT_STEP))
+ verifyRelativeEvents(arrayOf<Float>(0f),
+ arrayOf<Float>(-MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP))
}
private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 52b33db556e6..f371823473ef 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -51,9 +51,6 @@ import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.test.FakePermissionEnforcer;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.KeyEvent;
@@ -98,9 +95,6 @@ public class ProxyManagerTest {
private static final int STREAMED_CALLING_UID = 9876;
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private Context mMockContext;
@@ -243,7 +237,6 @@ public class ProxyManagerTest {
* app changes to the proxy device.
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER)
public void testUpdateProxyOfRunningAppsChange_changedUidIsStreamedApp_propagatesChange() {
final VirtualDeviceManagerInternal localVdm =
Mockito.mock(VirtualDeviceManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 1426d5d20419..ac27a971102a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -18,9 +18,9 @@ package com.android.server.accessibility.magnification;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static com.android.server.accessibility.Flags.FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX;
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
-import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -69,12 +69,14 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -92,6 +94,8 @@ import org.mockito.stubbing.Answer;
import org.testng.Assert;
import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
@RunWith(AndroidJUnit4.class)
public class FullScreenMagnificationControllerTest {
@@ -123,6 +127,7 @@ public class FullScreenMagnificationControllerTest {
final Resources mMockResources = mock(Resources.class);
final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
+ final InputManagerInternal mMockInputManager = mock(InputManagerInternal.class);
private final MagnificationAnimationCallback mAnimationCallback = mock(
MagnificationAnimationCallback.class);
private final MagnificationInfoChangedCallback mRequestObserver = mock(
@@ -160,6 +165,7 @@ public class FullScreenMagnificationControllerTest {
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+ when(mMockControllerCtx.getInputManager()).thenReturn(mMockInputManager);
when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
mResolver = new MockContentResolver();
@@ -282,10 +288,11 @@ public class FullScreenMagnificationControllerTest {
mFullScreenMagnificationController.magnificationRegionContains(displayId, 100,
100));
assertFalse(mFullScreenMagnificationController.reset(displayId, true));
- assertFalse(mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+ assertFalse(
+ mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, true, 0));
assertFalse(mFullScreenMagnificationController.setCenter(displayId, 100, 100, false, 1));
assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- 1.5f, 100, 100, false, 2));
+ 1.5f, 100, 100, true, false, 2));
assertTrue(mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
mFullScreenMagnificationController.getMagnificationRegion(displayId, new Region());
@@ -300,7 +307,6 @@ public class FullScreenMagnificationControllerTest {
}
@Test
- @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void testSetScale_noConnection_doNothing() {
register(TEST_DISPLAY);
@@ -310,7 +316,7 @@ public class FullScreenMagnificationControllerTest {
final float scale = 2.0f;
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertFalse(mFullScreenMagnificationController
- .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(TEST_DISPLAY, scale, center.x, center.y, true, false, SERVICE_ID_1));
assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
}
@@ -328,7 +334,7 @@ public class FullScreenMagnificationControllerTest {
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(displayId, scale, center.x, center.y, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
@@ -354,7 +360,7 @@ public class FullScreenMagnificationControllerTest {
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+ .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// New center should be halfway between original center and pivot
@@ -402,7 +408,7 @@ public class FullScreenMagnificationControllerTest {
float scale = 2.0f;
assertTrue(mFullScreenMagnificationController.setScale(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
- false, SERVICE_ID_1));
+ true, false, SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
@@ -437,7 +443,7 @@ public class FullScreenMagnificationControllerTest {
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
- newCenter.x, newCenter.y, mAnimationCallback, SERVICE_ID_1));
+ newCenter.x, newCenter.y, true, mAnimationCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -483,11 +489,11 @@ public class FullScreenMagnificationControllerTest {
final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
final float targetScale = 2.0f;
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- targetScale, center.x, center.y, false, SERVICE_ID_1));
+ targetScale, center.x, center.y, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- targetScale, center.x, center.y, mAnimationCallback, SERVICE_ID_1));
+ targetScale, center.x, center.y, true, mAnimationCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator, never()).start();
@@ -513,7 +519,7 @@ public class FullScreenMagnificationControllerTest {
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
MagnificationScaleProvider.MAX_SCALE + 1.0f,
- newCenter.x, newCenter.y, false, SERVICE_ID_1));
+ newCenter.x, newCenter.y, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -524,7 +530,7 @@ public class FullScreenMagnificationControllerTest {
// Verify that we can't zoom below 1x
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, 0.5f,
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
- false, SERVICE_ID_1));
+ true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
@@ -548,7 +554,7 @@ public class FullScreenMagnificationControllerTest {
// Off the edge to the top and left
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- scale, -100f, -200f, false, SERVICE_ID_1));
+ scale, -100f, -200f, true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
@@ -562,7 +568,7 @@ public class FullScreenMagnificationControllerTest {
// Off the edge to the bottom and right
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
- false, SERVICE_ID_1));
+ true, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
@@ -616,7 +622,7 @@ public class FullScreenMagnificationControllerTest {
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockWindowManager);
@@ -670,7 +676,7 @@ public class FullScreenMagnificationControllerTest {
// Upper left edges
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+ .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, true, false,
SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
@@ -682,7 +688,7 @@ public class FullScreenMagnificationControllerTest {
// Lower right edges
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+ .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, true, false,
SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
@@ -707,7 +713,7 @@ public class FullScreenMagnificationControllerTest {
float scale = 2.0f;
// First zoom in
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -750,7 +756,7 @@ public class FullScreenMagnificationControllerTest {
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mFullScreenMagnificationController
- .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -783,12 +789,12 @@ public class FullScreenMagnificationControllerTest {
register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1));
assertEquals(SERVICE_ID_1,
mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
assertTrue(mFullScreenMagnificationController
- .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_2));
assertEquals(SERVICE_ID_2,
mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
@@ -806,10 +812,10 @@ public class FullScreenMagnificationControllerTest {
register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
mFullScreenMagnificationController
- .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_1);
mFullScreenMagnificationController
- .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, true, false,
SERVICE_ID_2);
assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
@@ -870,7 +876,7 @@ public class FullScreenMagnificationControllerTest {
float scale = 2.5f;
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- scale, firstCenter.x, firstCenter.y, mAnimationCallback, SERVICE_ID_1));
+ scale, firstCenter.x, firstCenter.y, true, mAnimationCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockValueAnimator);
// Stubs the logic after the animation is started.
@@ -1073,7 +1079,7 @@ public class FullScreenMagnificationControllerTest {
float scale = 2.0f;
// setting animate parameter to true is differ from zoomIn2xToMiddle()
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- true, SERVICE_ID_1);
+ true, true, SERVICE_ID_1);
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
@@ -1104,7 +1110,7 @@ public class FullScreenMagnificationControllerTest {
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
@@ -1145,7 +1151,7 @@ public class FullScreenMagnificationControllerTest {
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- true, SERVICE_ID_1);
+ true, true, SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
when(mMockValueAnimator.isRunning()).thenReturn(true);
@@ -1332,7 +1338,7 @@ public class FullScreenMagnificationControllerTest {
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
- scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
+ scale, firstCenter.x, firstCenter.y, true, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(firstCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -1401,7 +1407,7 @@ public class FullScreenMagnificationControllerTest {
register(DISPLAY_0);
final float scale = 1.0f;
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
+ DISPLAY_0, scale, Float.NaN, Float.NaN, true, true, SERVICE_ID_1);
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ false, DISPLAY_0);
verify(mMockWindowManager).setFullscreenMagnificationActivated(DISPLAY_0, true);
@@ -1440,19 +1446,137 @@ public class FullScreenMagnificationControllerTest {
@Test
public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ register(TEST_DISPLAY);
final float persistedScale =
mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY);
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
- mFullScreenMagnificationController.setScale(DISPLAY_0, 1.0f, pivotPoint.x, pivotPoint.y,
- false, SERVICE_ID_1);
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 1.0f, pivotPoint.x, pivotPoint.y,
+ true, false, SERVICE_ID_1);
mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
persistedScale);
}
@Test
+ public void persistScale_setValuesOnMultipleDisplays() {
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ final PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, 3.0f, pivotPoint.x, pivotPoint.y,
+ true, false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_1, 4.0f, pivotPoint.x, pivotPoint.y,
+ true, false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(DISPLAY_1);
+
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(DISPLAY_0),
+ 3.0f);
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(DISPLAY_1),
+ 4.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX)
+ public void persistScale_setValue_notifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
+ /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+ verify(mMockInputManager, never()).setAccessibilityPointerIconScaleFactor(anyInt(),
+ anyFloat());
+
+ mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
+ 4.0f);
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX)
+ public void setScale_setNonTransientScale_notifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
+ /* isScaleTransient= */ false, /* animate= */ false, SERVICE_ID_1);
+
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX)
+ public void setScaleAndCenter_setTransientScale_notNotifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 3.0f, point.x,
+ point.y, /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+
+ verify(mRequestObserver).onFullScreenMagnificationChanged(anyInt(), any(Region.class),
+ any(MagnificationConfig.class));
+ verify(mMockInputManager, never()).setAccessibilityPointerIconScaleFactor(anyInt(),
+ anyFloat());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX)
+ public void setScaleAndCenter_setNonTransientScale_notifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 3.0f, point.x,
+ point.y, /* isScaleTransient= */ false, /* animate= */ false, SERVICE_ID_1);
+
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 3.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX)
+ public void setCenter_notNotifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 2.0f, point.x, point.y,
+ /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.setCenter(TEST_DISPLAY, point.x, point.y, false,
+ SERVICE_ID_1);
+
+ // Note that setCenter doesn't change scale, so it's not necessary to notify the input
+ // manager, but we currently do. The input manager skips redundant computation if the
+ // notified scale is the same as the previous call.
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY,
+ 2.0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX)
+ public void offsetMagnifiedRegion_notNotifyInput() {
+ register(TEST_DISPLAY);
+
+ PointF point = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 2.0f, point.x, point.y,
+ /* isScaleTransient= */ true, /* animate= */ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.offsetMagnifiedRegion(TEST_DISPLAY, 100, 50,
+ SERVICE_ID_1);
+
+ // Note that setCenter doesn't change scale, so it's not necessary to notify the input
+ // manager, but we currently do. The input manager skips redundant computation if the
+ // notified scale is the same as the previous call.
+ verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY,
+ 2.0f);
+ }
+
+ @Test
public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
setScaleToMagnifying();
@@ -1494,13 +1618,22 @@ public class FullScreenMagnificationControllerTest {
);
}
+ private static void waitForBackgroundThread() {
+ final CompletableFuture<Void> future = new CompletableFuture<>();
+ BackgroundThread.getHandler().post(() -> future.complete(null));
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException ignore) {
+ }
+ }
+
private void setScaleToMagnifying() {
register(DISPLAY_0);
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, pivotPoint.x, pivotPoint.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
}
private void initMockWindowManager() {
@@ -1543,7 +1676,7 @@ public class FullScreenMagnificationControllerTest {
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
- false, SERVICE_ID_1);
+ true, false, SERVICE_ID_1);
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index b745e6a7d4a5..9f5dd93054c3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -90,6 +90,7 @@ import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
+import com.android.server.input.InputManagerInternal;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
import com.android.server.wm.WindowManagerInternal;
@@ -227,9 +228,11 @@ public class FullScreenMagnificationGestureHandlerTest {
final FullScreenMagnificationController.ControllerContext mockController =
mock(FullScreenMagnificationController.ControllerContext.class);
final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+ final InputManagerInternal mockInputManager = mock(InputManagerInternal.class);
when(mockController.getContext()).thenReturn(mContext);
when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+ when(mockController.getInputManager()).thenReturn(mockInputManager);
when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
when(mockController.getAnimationDuration()).thenReturn(1000L);
@@ -1343,7 +1346,7 @@ public class FullScreenMagnificationGestureHandlerTest {
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
+ DEFAULT_Y, true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1364,7 +1367,7 @@ public class FullScreenMagnificationGestureHandlerTest {
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
+ DEFAULT_Y, true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1401,7 +1404,7 @@ public class FullScreenMagnificationGestureHandlerTest {
mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
+ DEFAULT_Y, true, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mMgh.transitionTo(mMgh.mPanningScalingState);
@@ -1417,7 +1420,7 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseMoveEventsDoNotMoveMagnifierViewport() {
runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
}
@@ -1438,7 +1441,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 5.6f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
@@ -1471,55 +1474,55 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() {
runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseDownEventsDoNotMoveMagnifierViewport() {
runDownDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusDownEventsDoNotMoveMagnifierViewport() {
runDownDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseUpEventsDoNotMoveMagnifierViewport() {
runUpDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusUpEventsDoNotMoveMagnifierViewport() {
runUpDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseMoveEventsMoveMagnifierViewport() {
final EventCaptor eventCaptor = new EventCaptor();
mMgh.setNext(eventCaptor);
@@ -1530,7 +1533,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 6.2f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
MotionEvent event = mouseEvent(centerX, centerY, ACTION_HOVER_MOVE);
send(event, InputDevice.SOURCE_MOUSE);
fastForward(20);
@@ -1571,7 +1574,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
// HOVER_MOVE should change magnifier viewport.
MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE);
@@ -1615,7 +1618,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 5.3f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
send(event, source);
fastForward(20);
@@ -1649,7 +1652,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 2.7f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
send(event, source);
fastForward(20);
@@ -1685,7 +1688,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 3.8f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
@@ -1722,7 +1725,7 @@ public class FullScreenMagnificationGestureHandlerTest {
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
mFullScreenMagnificationController.setScaleAndCenter(
- DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 6d27dddfc357..06ebe6e28809 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -307,7 +307,8 @@ public class MagnificationConnectionManagerTest {
mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
- mMagnificationConnectionManager.setScale(TEST_DISPLAY, 10.0f);
+ mMagnificationConnectionManager.setScale(TEST_DISPLAY,
+ MagnificationScaleProvider.MAX_SCALE * 2.f);
assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY),
MagnificationScaleProvider.MAX_SCALE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 25281774cd95..c878799109dc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -19,9 +19,13 @@ package com.android.server.accessibility.magnification;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -53,10 +57,6 @@ import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.testing.DexmakerShareClassLoaderRule;
@@ -76,8 +76,8 @@ import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -96,9 +96,6 @@ import org.mockito.stubbing.Answer;
@RunWith(AndroidJUnit4.class)
public class MagnificationControllerTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
private static final int TEST_SERVICE_ID = 1;
private static final Region INITIAL_SCREEN_MAGNIFICATION_REGION =
@@ -154,6 +151,8 @@ public class MagnificationControllerTest {
private WindowManagerInternal mWindowManagerInternal;
@Mock
private WindowManagerInternal.AccessibilityControllerInternal mA11yController;
+ @Mock
+ private InputManagerInternal mInputManagerInternal;
@Mock
private DisplayManagerInternal mDisplayManagerInternal;
@@ -200,6 +199,7 @@ public class MagnificationControllerTest {
when(mControllerCtx.getContext()).thenReturn(mContext);
when(mControllerCtx.getTraceManager()).thenReturn(mTraceManager);
when(mControllerCtx.getWindowManager()).thenReturn(mWindowManagerInternal);
+ when(mControllerCtx.getInputManager()).thenReturn(mInputManagerInternal);
when(mControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mControllerCtx.getAnimationDuration()).thenReturn(1000L);
when(mControllerCtx.newValueAnimator()).thenReturn(mValueAnimator);
@@ -417,7 +417,7 @@ public class MagnificationControllerTest {
assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY,
DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
- true, MAGNIFICATION_GESTURE_HANDLER_ID);
+ true, true, MAGNIFICATION_GESTURE_HANDLER_ID);
verify(mTransitionCallBack).onResult(TEST_DISPLAY, false);
}
@@ -467,7 +467,7 @@ public class MagnificationControllerTest {
assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
eq(DEFAULT_SCALE), eq(MAGNIFIED_CENTER_X), eq(MAGNIFIED_CENTER_Y),
- any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
+ eq(false), any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
}
@Test
@@ -484,7 +484,7 @@ public class MagnificationControllerTest {
verify(mScreenMagnificationController, never()).setScaleAndCenter(anyInt(),
anyFloat(), anyFloat(), anyFloat(),
- anyBoolean(), anyInt());
+ anyBoolean(), anyBoolean(), anyInt());
}
@Test
@@ -546,7 +546,7 @@ public class MagnificationControllerTest {
config, animate, TEST_SERVICE_ID);
verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
/* scale= */ anyFloat(), /* centerX= */ anyFloat(), /* centerY= */ anyFloat(),
- mCallbackArgumentCaptor.capture(), /* id= */ anyInt());
+ anyBoolean(), mCallbackArgumentCaptor.capture(), /* id= */ anyInt());
mCallbackArgumentCaptor.getValue().onResult(true);
mMockConnection.invokeCallbacks();
@@ -616,7 +616,7 @@ public class MagnificationControllerTest {
@Test
public void magnifyThroughExternalRequest_showMagnificationButton() {
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE,
- MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, false, TEST_SERVICE_ID);
+ MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, false, TEST_SERVICE_ID);
// The first time is trigger when fullscreen mode is activated.
// The second time is triggered when magnification spec is changed.
@@ -638,7 +638,7 @@ public class MagnificationControllerTest {
mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), eq(newScale),
- anyFloat(), anyFloat(), anyBoolean(), anyInt());
+ anyFloat(), anyFloat(), anyBoolean(), anyBoolean(), anyInt());
verify(mScreenMagnificationController).persistScale(eq(TEST_DISPLAY));
}
@@ -656,6 +656,90 @@ public class MagnificationControllerTest {
}
@Test
+ public void scaleMagnificationByStep_fullscreenMode_stepInAndOut() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 1.0f, false);
+ reset(mScreenMagnificationController);
+
+ // Verify the zoom scale factor increases by
+ // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+ // .ZOOM_STEP_SCALE_FACTOR} and the center coordinates are
+ // unchanged (Float.NaN as values denotes unchanged center).
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(MagnificationController
+ .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR),
+ eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+ verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(SCALE_MIN_VALUE), eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void scaleMagnificationByStep_testMaxScaling() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+ reset(mScreenMagnificationController);
+
+ float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ while (currentScale < SCALE_MAX_VALUE) {
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN)).isTrue();
+ final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ assertThat(nextScale).isGreaterThan(currentScale);
+ currentScale = nextScale;
+ }
+
+ assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE);
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN)).isFalse();
+ }
+
+ @Test
+ public void scaleMagnificationByStep_testMinScaling() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MAX_VALUE, false);
+ reset(mScreenMagnificationController);
+
+ float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ while (currentScale > SCALE_MIN_VALUE) {
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT)).isTrue();
+ final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ assertThat(nextScale).isLessThan(currentScale);
+ currentScale = nextScale;
+ }
+
+ assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE);
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT)).isFalse();
+ }
+
+ @Test
+ public void scaleMagnificationByStep_windowedMode_stepInAndOut() throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+ reset(mMagnificationConnectionManager);
+
+ // Verify the zoom scale factor increases by
+ // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+ // .ZOOM_STEP_SCALE_FACTOR}.
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+ eq(MagnificationController
+ .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR));
+
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+ verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+ eq(SCALE_MIN_VALUE));
+ }
+
+ @Test
public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
@@ -681,7 +765,7 @@ public class MagnificationControllerTest {
final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
config.getScale(), config.getCenterX(), config.getCenterY(),
- true, TEST_SERVICE_ID);
+ true, true, TEST_SERVICE_ID);
// The notify method is triggered when setting magnification enabled.
// The setScaleAndCenter call should not trigger notify method due to same scale and center.
@@ -930,7 +1014,7 @@ public class MagnificationControllerTest {
public void onWindowModeActivated_fullScreenIsActivatedByExternal_fullScreenIsDisabled() {
mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
- true, TEST_SERVICE_ID);
+ true, true, TEST_SERVICE_ID);
mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
@@ -1273,8 +1357,7 @@ public class MagnificationControllerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
- public void onFullscreenMagnificationActivationState_systemUiBorderFlagOn_notifyConnection() {
+ public void onFullscreenMagnificationActivationState_notifyConnection() {
mMagnificationController.onFullScreenMagnificationActivationState(
TEST_DISPLAY, /* activated= */ true);
@@ -1282,17 +1365,6 @@ public class MagnificationControllerTest {
.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, /* activated= */ true);
}
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
- public void
- onFullscreenMagnificationActivationState_systemUiBorderFlagOff_neverNotifyConnection() {
- mMagnificationController.onFullScreenMagnificationActivationState(
- TEST_DISPLAY, /* activated= */ true);
-
- verify(mMagnificationConnectionManager, never())
- .onFullscreenMagnificationActivationChanged(TEST_DISPLAY, /* activated= */ true);
- }
-
private void setMagnificationEnabled(int mode) throws RemoteException {
setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
}
@@ -1317,7 +1389,8 @@ public class MagnificationControllerTest {
}
if (mode == MODE_FULLSCREEN) {
mScreenMagnificationController.setScaleAndCenter(displayId, DEFAULT_SCALE, centerX,
- centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ centerY, true, true,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
} else {
mMagnificationConnectionManager.enableWindowMagnification(displayId, DEFAULT_SCALE,
centerX, centerY, null, TEST_SERVICE_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index d80a1f056e94..45c157d1c1a0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -93,7 +93,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() {
final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
@@ -108,7 +108,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() {
final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
@@ -123,7 +123,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() {
final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
@@ -138,7 +138,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() {
final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index 0218a7835586..000000000000
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 467c15dd2a75..4d6f5e5df656 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -55,6 +55,8 @@ public final class BatteryStatsServiceTest {
File systemDir = context.getCacheDir();
Handler handler = new Handler(mBgThread.getLooper());
mBatteryStatsService = new BatteryStatsService(context, systemDir);
+ mBatteryStatsService.setRailsStatsCollectionEnabled(false);
+ mBatteryStatsService.systemServicesReady();
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 31bf5f046cff..4981ceb944c2 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -61,6 +61,7 @@ public class OomAdjusterTests {
private static final long USAGE_STATS_INTERACTION = 10 * 60 * 1000L;
private static final long SERVICE_USAGE_INTERACTION = 60 * 1000;
+ @SuppressWarnings("GuardedBy")
@BeforeClass
public static void setUpOnce() {
sContext = getInstrumentation().getTargetContext();
@@ -92,8 +93,11 @@ public class OomAdjusterTests {
return true;
}
};
- sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null,
- injector);
+ sService.mProcessStateController = new ProcessStateController.Builder(sService,
+ sService.mProcessList, null)
+ .setOomAdjusterInjector(injector)
+ .build();
+ sService.mOomAdjuster = sService.mProcessStateController.getOomAdjuster();
LocalServices.addService(UsageStatsManagerInternal.class,
mock(UsageStatsManagerInternal.class));
sService.mUsageStatsService = LocalServices.getService(UsageStatsManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 390eb937fe25..2fe6918630f6 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -181,12 +181,14 @@ public class UserControllerTest {
Intent.ACTION_USER_STARTING);
private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet(
+ 0, // for startUserInternalOnHandler
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
USER_START_MSG,
USER_CURRENT_MSG);
private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ 0, // for startUserInternalOnHandler
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
@@ -374,7 +376,7 @@ public class UserControllerTest {
// and the cascade effect goes on...). In fact, a better approach would to not assert the
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
- .containsExactly(USER_START_MSG);
+ .containsExactly(/* for startUserInternalOnHandler */ 0, USER_START_MSG);
}
private void startUserAssertions(
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index c970a3e34d12..840e5c58078b 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -65,7 +65,6 @@ public class AppOpsActiveWatcherTest {
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS
);
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
index 7f2327aa4f24..e3eca6d5fd83 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -58,7 +58,6 @@ public class AppOpsDeviceAwareServiceTest {
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS);
private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index 1abd4eb6157f..b0846f62628c 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,16 +22,14 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.FakeAssociationRule;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -42,8 +40,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
-import java.util.concurrent.atomic.AtomicInteger;
-
/**
* Tests watching noted ops.
*/
@@ -51,7 +47,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
@Rule
- public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -119,19 +115,12 @@ public class AppOpsNotedWatcherTest {
public void testWatchNotedOpsForExternalDevice() {
final AppOpsManager.OnOpNotedListener listener = mock(
AppOpsManager.OnOpNotedListener.class);
- final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
- VirtualDeviceManager.class);
- AtomicInteger virtualDeviceId = new AtomicInteger();
- runWithShellPermissionIdentity(() -> {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- virtualDeviceManager.createVirtualDevice(
- mFakeAssociationRule.getAssociationInfo().getId(),
- new VirtualDeviceParams.Builder().setName("virtual_device").build());
- virtualDeviceId.set(virtualDevice.getDeviceId());
- });
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final int virtualDeviceId = virtualDevice.getDeviceId();
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId.get());
+ virtualDeviceId);
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -142,7 +131,7 @@ public class AppOpsNotedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId),
eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
index c4b3c149bd8d..5d7ffe91e67d 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.app.PropertyInvalidatedCache;
import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.os.FileUtils;
@@ -69,6 +70,7 @@ public class AppOpsRecentAccessPersistenceTest {
@Before
public void setUp() {
+ PropertyInvalidatedCache.disableForTestMode();
when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true);
LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService);
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index 8a6ba4d484f7..d46fb90f40d6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,8 +16,6 @@
package com.android.server.appop;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -28,11 +26,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.FakeAssociationRule;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -43,15 +40,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
-import java.util.concurrent.atomic.AtomicInteger;
-
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
@Rule
- public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -124,20 +119,13 @@ public class AppOpsStartedWatcherTest {
@Test
public void testWatchStartedOpsForExternalDevice() {
- final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
- VirtualDeviceManager.class);
- AtomicInteger virtualDeviceId = new AtomicInteger();
- runWithShellPermissionIdentity(() -> {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- virtualDeviceManager.createVirtualDevice(
- mFakeAssociationRule.getAssociationInfo().getId(),
- new VirtualDeviceParams.Builder().setName("virtual_device").build());
- virtualDeviceId.set(virtualDevice.getDeviceId());
- });
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final int virtualDeviceId = virtualDevice.getDeviceId();
final OnOpStartedListener listener = mock(OnOpStartedListener.class);
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId.get());
+ virtualDeviceId);
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -150,7 +138,7 @@ public class AppOpsStartedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId),
eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 464515632997..de5564cb7704 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -16,8 +16,12 @@
package com.android.server.audio;
+import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.absVolumeIndexFix;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -107,16 +111,17 @@ public class AudioDeviceVolumeManagerTest {
mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName);
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ eq(AudioManager.STREAM_MUSIC), eq(minIndex), anyBoolean(),
+ eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName);
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ AudioManager.STREAM_MUSIC, midIndex, false, AudioSystem.DEVICE_OUT_USB_DEVICE);
}
@Test
- @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+ @RequiresFlagsDisabled({FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME, FLAG_ABS_VOLUME_INDEX_FIX})
public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception {
AudioManager am = mContext.getSystemService(AudioManager.class);
final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC);
@@ -149,7 +154,7 @@ public class AudioDeviceVolumeManagerTest {
// Stream volume changes
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, targetIndex,
+ AudioManager.STREAM_MUSIC, targetIndex, false,
AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
@@ -160,7 +165,7 @@ public class AudioDeviceVolumeManagerTest {
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, maxIndex,
+ AudioManager.STREAM_MUSIC, maxIndex, false,
AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
@@ -177,6 +182,7 @@ public class AudioDeviceVolumeManagerTest {
final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla");
final int maxPreScaleIndex = 3;
+ int passedIndex = maxIndex;
for (int i = 0; i < maxPreScaleIndex; i++) {
final VolumeInfo volCur = new VolumeInfo.Builder(volMedia)
@@ -185,10 +191,13 @@ public class AudioDeviceVolumeManagerTest {
mAudioService.setDeviceVolume(volCur, bleDevice, mPackageName);
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = i + 1;
+ }
// Stream volume changes
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, maxIndex,
- AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ AudioManager.STREAM_MUSIC, passedIndex, false,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
// Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
@@ -197,8 +206,11 @@ public class AudioDeviceVolumeManagerTest {
mAudioService.setDeviceVolume(volIndex4, bleDevice, mPackageName);
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = 4;
+ }
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, maxIndex,
- AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ AudioManager.STREAM_MUSIC, passedIndex, false,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index b7100ea00a40..96c6cbc9ec64 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -15,6 +15,9 @@
*/
package com.android.server.audio;
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioManager.GET_DEVICES_INPUTS;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -27,16 +30,22 @@ import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
+import android.media.AudioDeviceAttributes;
import android.media.AudioSystem;
import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
+import android.util.IntArray;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.media.flags.Flags;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -53,6 +62,7 @@ public class AudioServiceTest {
private static final String TAG = "AudioServiceTest";
private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
+ private static final int DEFAULT_INPUT_GAIN_INDEX = 50;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -73,12 +83,15 @@ public class AudioServiceTest {
private static boolean sLooperPrepared = false;
+ private TestLooper mTestLooper;
+
@Before
public void setUp() throws Exception {
if (!sLooperPrepared) {
Looper.prepare();
sLooperPrepared = true;
}
+ mTestLooper = new TestLooper();
mContext = InstrumentationRegistry.getTargetContext();
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
mSettingsAdapter = new NoOpSettingsAdapter();
@@ -86,8 +99,11 @@ public class AudioServiceTest {
when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
- mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
+ mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
+ mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer,
+ mMockPermissionProvider, r -> r.run());
+
+ mTestLooper.dispatchAll();
}
/**
@@ -107,7 +123,7 @@ public class AudioServiceTest {
Assert.assertEquals("mic mute reporting wrong value",
muted, mAudioService.isMicrophoneMuted());
// verify the intent for mic mute changed is supposed to be fired
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ mTestLooper.dispatchAll();
verify(mSpySystemServer, times(1))
.sendMicrophoneMuteChangedIntent();
reset(mSpySystemServer);
@@ -132,7 +148,7 @@ public class AudioServiceTest {
Assert.assertEquals("mic mute reporting wrong value",
!muted, mAudioService.isMicrophoneMuted());
// verify the intent for mic mute changed is supposed to be fired
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ mTestLooper.dispatchAll();
verify(mSpySystemServer, times(1))
.sendMicrophoneMuteChangedIntent();
reset(mSpySystemServer);
@@ -143,8 +159,7 @@ public class AudioServiceTest {
public void testRingNotifAlias() throws Exception {
Log.i(TAG, "running testRingNotifAlias");
Assert.assertNotNull(mAudioService);
- // TODO add initialization message that can be caught here instead of sleeping
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization
+ mTestLooper.dispatchAll(); // wait for full AudioService initialization
// test with aliasing RING and NOTIFICATION
mAudioService.setNotifAliasRingForTest(true);
@@ -155,7 +170,7 @@ public class AudioServiceTest {
mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION,
ringVol, 0, "bla");
mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringMaxVol, 0, "bla");
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ mTestLooper.dispatchAll();
Assert.assertEquals(ringMaxVol,
mAudioService.getStreamVolume(AudioSystem.STREAM_NOTIFICATION));
@@ -202,4 +217,43 @@ public class AudioServiceTest {
reset(mSpySystemServer);
}
}
+
+ /** Test input gain index setter and getter */
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void testInputGainIndex() throws Exception {
+ Log.i(TAG, "running testInputGainIndex");
+ Assert.assertNotNull(mAudioService);
+
+ IntArray internalDeviceTypes = new IntArray();
+ int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:"
+ + status);
+ }
+
+ // Make sure TYPE_BUILTIN_MIC, aka DEVICE_IN_BUILTIN_MIC in terms of internal device type,
+ // is supported.
+ if (!internalDeviceTypes.contains(AudioSystem.DEVICE_IN_BUILTIN_MIC)) {
+ return;
+ }
+
+ AudioDeviceAttributes ada =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT, TYPE_BUILTIN_MIC, /* address= */ "");
+
+ Assert.assertEquals(
+ "default input gain index reporting wrong value",
+ DEFAULT_INPUT_GAIN_INDEX,
+ mAudioService.getInputGainIndex(ada));
+
+ int inputGainIndex = 20;
+ mAudioService.setInputGainIndex(ada, inputGainIndex);
+ mTestLooper.dispatchAll();
+
+ Assert.assertEquals(
+ "input gain index reporting wrong value",
+ inputGainIndex,
+ mAudioService.getInputGainIndex(ada));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
index fa94821d4ff2..3c27af4b0dfc 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
@@ -34,6 +34,8 @@ import androidx.test.core.app.ApplicationProvider;
import com.google.common.truth.Expect;
+import com.android.server.utils.EventLogger;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,6 +69,8 @@ public final class FadeOutManagerTest {
PlayerBase.PlayerIdCard mMockPlayerIdCard;
@Mock
AudioPlaybackConfiguration mMockPlaybackConfiguration;
+ @Mock
+ EventLogger mMockEventLogger;
@Rule
public final Expect expect = Expect.create();
@@ -193,7 +197,7 @@ public final class FadeOutManagerTest {
String packageName, int uid, int flags) {
MediaFocusControl mfc = new MediaFocusControl(mContext, null);
return new FocusRequester(aa, AudioManager.AUDIOFOCUS_GAIN, flags, null, null, clientId,
- null, packageName, uid, mfc, 1);
+ null, packageName, uid, mfc, 1, mMockEventLogger);
}
private PlayerBase.PlayerIdCard createPlayerIdCard(AudioAttributes aa, int playerType) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
index 84c0ab38ca48..0d44021bae09 100644
--- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
@@ -314,12 +314,13 @@ public class LoudnessCodecHelperTest {
AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS);
assumeTrue(devIdx < devicesStatic.length);
Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + devIdx);
- int deviceId = devicesStatic[devIdx].getId();
+ int[] deviceIds = new int[1];
+ deviceIds[0] = devicesStatic[devIdx].getId();
PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class);
AudioPlaybackConfiguration apc =
new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/myPid());
- apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId);
+ apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceIds);
apc.handleSessionIdEvent(sessionId);
apc.handleAudioAttributesEvent(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 96ac5d251ffd..ce59a86c6ca3 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -132,12 +132,13 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter {
}
@Override
- public int setStreamVolumeIndexAS(int stream, int index, int device) {
+ public int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) {
return AudioSystem.AUDIO_STATUS_OK;
}
@Override
- public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) {
+ public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, boolean muted,
+ int device) {
return AudioSystem.AUDIO_STATUS_OK;
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index beed0a3d413c..6b41c434b80f 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -18,6 +18,7 @@ package com.android.server.audio;
import static android.media.AudioManager.ADJUST_LOWER;
import static android.media.AudioManager.ADJUST_MUTE;
import static android.media.AudioManager.ADJUST_RAISE;
+import static android.media.AudioManager.ADJUST_UNMUTE;
import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER;
import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_SCO;
import static android.media.AudioManager.DEVICE_OUT_SPEAKER;
@@ -39,14 +40,15 @@ import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
-import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
+import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.FLAG_RING_MY_CAR;
+import static com.android.media.audio.Flags.absVolumeIndexFix;
import static com.google.common.truth.Truth.assertThat;
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.assertTrue;
import static org.junit.Assume.assumeFalse;
@@ -179,6 +181,10 @@ public class VolumeHelperTest {
}
return mStreamDevice.get(stream);
}
+
+ public void setMuteAffectedStreams(int muteAffectedStreams) {
+ mMuteAffectedStreams = muteAffectedStreams;
+ }
}
private static class TestDeviceVolumeBehaviorDispatcherStub
@@ -222,6 +228,7 @@ public class VolumeHelperTest {
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer,
mMockPermissionProvider);
+ mAudioService.setMuteAffectedStreams(AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED);
mTestLooper.dispatchAll();
prepareAudioServiceState();
@@ -257,6 +264,8 @@ public class VolumeHelperTest {
for (int streamType : usedStreamTypes) {
mAudioService.setStreamVolume(streamType, DEFAULT_STREAM_VOLUME, /*flags=*/0,
mContext.getOpPackageName());
+ mAudioService.adjustStreamVolume(streamType, ADJUST_UNMUTE, /*flags=*/0,
+ mContext.getOpPackageName());
}
if (!mIsAutomotive) {
@@ -289,6 +298,9 @@ public class VolumeHelperTest {
// --------------- Volume Stream APIs ---------------
@Test
public void setStreamVolume_callsASSetStreamVolumeIndex() throws Exception {
+ assumeFalse("Skipping setStreamVolume_callsASSetStreamVolumeIndex on automotive",
+ mIsAutomotive);
+
int newIndex = circularNoMinMaxIncrementVolume(STREAM_MUSIC);
mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
@@ -297,7 +309,20 @@ public class VolumeHelperTest {
mTestLooper.dispatchAll();
verify(mSpyAudioSystem).setStreamVolumeIndexAS(
- eq(STREAM_MUSIC), eq(newIndex), eq(DEVICE_OUT_USB_DEVICE));
+ STREAM_MUSIC, newIndex, /*muted=*/false, DEVICE_OUT_USB_DEVICE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_RING_MY_CAR)
+ public void adjustStreamVolume_adjustMute_callsASSetStreamVolumeIndex() throws Exception {
+ int currentIndex = mAudioService.getStreamVolume(STREAM_MUSIC);
+
+ mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_MUTE, /*flags=*/0,
+ mContext.getOpPackageName());
+ mTestLooper.dispatchAll();
+
+ verify(mSpyAudioSystem).setStreamVolumeIndexAS(
+ eq(STREAM_MUSIC), eq(currentIndex), /*muted=*/eq(true), anyInt());
}
@Test
@@ -312,17 +337,23 @@ public class VolumeHelperTest {
@Test
public void adjustStreamVolume_callsASSetStreamVolumeIndex() throws Exception {
+ assumeFalse("Skipping adjustStreamVolume_callsASSetStreamVolumeIndex on automotive",
+ mIsAutomotive);
+
mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_LOWER, /*flags=*/0,
mContext.getOpPackageName());
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+ eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(DEVICE_OUT_USB_DEVICE));
}
@Test
public void handleVolumeKey_callsASSetStreamVolumeIndex() throws Exception {
+ assumeFalse("Skipping handleVolumeKey_callsASSetStreamVolumeIndex on automotive",
+ mIsAutomotive);
+
final KeyEvent keyEvent = new KeyEvent(ACTION_DOWN, KEYCODE_VOLUME_UP);
mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
@@ -331,27 +362,30 @@ public class VolumeHelperTest {
mTestLooper.dispatchAll();
verify(mSpyAudioSystem).setStreamVolumeIndexAS(
- eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+ eq(STREAM_MUSIC), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE));
}
// --------------- Volume Group APIs ---------------
@Test
public void setVolumeGroupVolumeIndex_callsASSetVolumeIndexForAttributes() throws Exception {
+ assumeFalse(
+ "Skipping setVolumeGroupVolumeIndex_callsASSetVolumeIndexForAttributes on "
+ + "automotive", mIsAutomotive);
assumeNotNull(mAudioMusicVolumeGroup);
mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(),
circularNoMinMaxIncrementVolume(STREAM_MUSIC), /*flags=*/0,
- mContext.getOpPackageName(), /*attributionTag*/null);
+ mContext.getOpPackageName(), /*attributionTag*/null);
mTestLooper.dispatchAll();
- verify(mSpyAudioSystem).setVolumeIndexForAttributes(
- any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+ verify(mSpyAudioSystem).setVolumeIndexForAttributes(any(), anyInt(), eq(false),
+ eq(DEVICE_OUT_USB_DEVICE));
}
@Test
- public void adjustVolumeGroupVolume_callsASSetVolumeIndexForAttributes() throws Exception {
+ public void adjustVolumeGroupVolume_callsASSetStreamVolumeIndexAS() throws Exception {
assumeNotNull(mAudioMusicVolumeGroup);
mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
@@ -359,12 +393,29 @@ public class VolumeHelperTest {
ADJUST_LOWER, /*flags=*/0, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- verify(mSpyAudioSystem).setVolumeIndexForAttributes(
- any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+ // adjust calls setStreamVolumeIndexAS instead of setVolumeIndexForAttributes
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ anyInt(), anyInt(), anyBoolean(), eq(DEVICE_OUT_USB_DEVICE));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_RING_MY_CAR)
+ public void adjustVolumeGroupVolume_adjustMute_callsASSetStreamVolumeIndexAS()
+ throws Exception {
+ assumeNotNull(mAudioMusicVolumeGroup);
+
+ mAudioService.adjustVolumeGroupVolume(mAudioMusicVolumeGroup.getId(),
+ ADJUST_MUTE, /*flags=*/0, mContext.getOpPackageName());
+ mTestLooper.dispatchAll();
+
+ // adjust calls setStreamVolumeIndexAS instead of setVolumeIndexForAttributes
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ anyInt(), anyInt(), eq(true), anyInt());
}
@Test
public void check_getVolumeGroupVolumeIndex() throws Exception {
+ assumeFalse("Skipping check_getVolumeGroupVolumeIndex on automotive", mIsAutomotive);
assumeNotNull(mAudioMusicVolumeGroup);
int newIndex = circularNoMinMaxIncrementVolume(STREAM_MUSIC);
@@ -373,10 +424,9 @@ public class VolumeHelperTest {
newIndex, /*flags=*/0, mContext.getOpPackageName(), /*attributionTag*/null);
mTestLooper.dispatchAll();
- assertEquals(mAudioService.getVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId()),
- newIndex);
- assertEquals(mAudioService.getStreamVolume(STREAM_MUSIC),
- newIndex);
+ assertEquals(newIndex,
+ mAudioService.getVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId()));
+ assertEquals(newIndex, mAudioService.getStreamVolume(STREAM_MUSIC));
}
@Test
@@ -424,13 +474,14 @@ public class VolumeHelperTest {
@Test
public void check_isStreamAffectedByMute() {
- assertFalse(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL));
+ assertTrue(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL));
}
// --------------------- Volume Flag Check --------------------
@Test
public void flagAbsVolume_onBtDevice_changesVolume() throws Exception {
+ assumeFalse("Skipping flagAbsVolume_onBtDevice_changesVolume on automotive", mIsAutomotive);
mAudioService.setDeviceForStream(STREAM_NOTIFICATION, DEVICE_OUT_BLE_SPEAKER);
int newIndex = circularNoMinMaxIncrementVolume(STREAM_NOTIFICATION);
@@ -438,14 +489,14 @@ public class VolumeHelperTest {
mContext.getOpPackageName());
mTestLooper.dispatchAll();
verify(mSpyAudioSystem).setStreamVolumeIndexAS(
- eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER));
+ eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
reset(mSpyAudioSystem);
mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER,
FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName());
mTestLooper.dispatchAll();
verify(mSpyAudioSystem).setStreamVolumeIndexAS(
- eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER));
+ eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
}
@Test
@@ -457,13 +508,13 @@ public class VolumeHelperTest {
mContext.getOpPackageName());
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
- eq(STREAM_NOTIFICATION), eq(newIndex), eq(DEVICE_OUT_BLE_SPEAKER));
+ eq(STREAM_NOTIFICATION), eq(newIndex), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER,
FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName());
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
- eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER));
+ eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
}
@Test
@@ -493,7 +544,7 @@ public class VolumeHelperTest {
mContext.getOpPackageName());
mTestLooper.dispatchAll();
- assertEquals(mAudioService.getRingerModeInternal(), RINGER_MODE_VIBRATE);
+ assertEquals(RINGER_MODE_VIBRATE, mAudioService.getRingerModeInternal());
}
// --------------------- Permission tests ---------------------
@@ -509,7 +560,7 @@ public class VolumeHelperTest {
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
- eq(STREAM_MUSIC), anyInt(), anyInt());
+ eq(STREAM_MUSIC), anyInt(), eq(false), anyInt());
}
@Test
@@ -523,7 +574,7 @@ public class VolumeHelperTest {
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
- eq(STREAM_VOICE_CALL), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+ eq(STREAM_VOICE_CALL), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE));
mAudioService.setDeviceForStream(STREAM_BLUETOOTH_SCO, DEVICE_OUT_BLUETOOTH_SCO);
mAudioService.adjustStreamVolume(STREAM_BLUETOOTH_SCO, ADJUST_MUTE, /*flags=*/0,
@@ -531,12 +582,13 @@ public class VolumeHelperTest {
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
- eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+ eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE));
}
// ----------------- AudioDeviceVolumeManager -----------------
@Test
public void setDeviceVolume_checkIndex() {
+ assumeFalse("Skipping setDeviceVolume_checkIndex on automotive", mIsAutomotive);
final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
final int midIndex = (minIndex + maxIndex) / 2;
@@ -552,23 +604,19 @@ public class VolumeHelperTest {
mAudioService.setDeviceVolume(volMin, usbDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- if (!mIsAutomotive) {
- // there is a min/max index mismatch in automotive
- assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice,
- mContext.getOpPackageName()), volMin);
- }
+ // there is a min/max index mismatch in automotive
+ assertEquals(volMin.getVolumeIndex(), mAudioService.getDeviceVolume(volMin, usbDevice,
+ mContext.getOpPackageName()).getVolumeIndex());
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
+ eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- if (!mIsAutomotive) {
- // there is a min/max index mismatch in automotive
- assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice,
- mContext.getOpPackageName()), volMid);
- }
+ // there is a min/max index mismatch in automotive
+ assertEquals(volMid.getVolumeIndex(), mAudioService.getDeviceVolume(volMid, usbDevice,
+ mContext.getOpPackageName()).getVolumeIndex());
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
+ eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
}
@Test
@@ -602,13 +650,11 @@ public class VolumeHelperTest {
mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- assertEquals(
- mAudioService.getDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()),
- volCur);
+ assertEquals(volCur,
+ mAudioService.getDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()));
// Stream volume changes
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, targetIndex,
- AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ STREAM_MUSIC, targetIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
// Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
@@ -617,17 +663,14 @@ public class VolumeHelperTest {
mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- assertEquals(
- mAudioService.getDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()),
- volIndex4);
+ assertEquals(volIndex4,
+ mAudioService.getDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()));
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, maxIndex,
- AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ STREAM_MUSIC, maxIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
@Test
@RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
- @RequiresFlagsDisabled(FLAG_ABS_VOLUME_INDEX_FIX)
public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception {
final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
@@ -638,6 +681,7 @@ public class VolumeHelperTest {
final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla");
final int maxPreScaleIndex = 3;
+ int passedIndex = maxIndex;
for (int i = 0; i < maxPreScaleIndex; i++) {
final VolumeInfo volCur = new VolumeInfo.Builder(volMedia)
@@ -646,10 +690,12 @@ public class VolumeHelperTest {
mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = i + 1;
+ }
// Stream volume changes
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, maxIndex,
- AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ STREAM_MUSIC, passedIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
// Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
@@ -658,9 +704,11 @@ public class VolumeHelperTest {
mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = 4;
+ }
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, maxIndex,
- AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ STREAM_MUSIC, passedIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
// ---------------- DeviceVolumeBehaviorTest ----------------
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 83753ccf1cbe..c7efa318af99 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -91,6 +92,8 @@ public class AuthServiceTest {
private static final String TEST_OP_PACKAGE_NAME = "test_package";
+ private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
+
private AuthService mAuthService;
@Rule
@@ -256,12 +259,11 @@ public class AuthServiceTest {
final Binder token = new Binder();
final PromptInfo promptInfo = new PromptInfo();
final long sessionId = 0;
- final int userId = 0;
mAuthService.mImpl.authenticate(
token,
sessionId,
- userId,
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
promptInfo);
@@ -269,7 +271,7 @@ public class AuthServiceTest {
verify(mBiometricService).authenticate(
eq(token),
eq(sessionId),
- eq(userId),
+ eq(mUserId),
eq(mReceiver),
eq(TEST_OP_PACKAGE_NAME),
eq(promptInfo));
@@ -285,12 +287,11 @@ public class AuthServiceTest {
final Binder token = new Binder();
final PromptInfo promptInfo = new PromptInfo();
final long sessionId = 0;
- final int userId = 0;
mAuthService.mImpl.authenticate(
token,
sessionId,
- userId,
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
promptInfo);
@@ -298,7 +299,7 @@ public class AuthServiceTest {
verify(mBiometricService, never()).authenticate(
eq(token),
eq(sessionId),
- eq(userId),
+ eq(mUserId),
eq(mReceiver),
eq(TEST_OP_PACKAGE_NAME),
eq(promptInfo));
@@ -312,12 +313,11 @@ public class AuthServiceTest {
final PromptInfo promptInfo = new PromptInfo();
final long sessionId = 0;
- final int userId = 0;
mAuthService.mImpl.authenticate(
null /* token */,
sessionId,
- userId,
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
promptInfo);
@@ -337,7 +337,7 @@ public class AuthServiceTest {
mAuthService.mImpl.authenticate(
token,
0, /* sessionId */
- 0, /* userId */
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
new PromptInfo());
@@ -355,7 +355,7 @@ public class AuthServiceTest {
mAuthService.mImpl.authenticate(
token,
0, /* sessionId */
- 0, /* userId */
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
new PromptInfo());
@@ -413,20 +413,19 @@ public class AuthServiceTest {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
- final int userId = 0;
final int expectedResult = BIOMETRIC_SUCCESS;
final int authenticators = 0;
when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt(), anyInt()))
.thenReturn(expectedResult);
final int result = mAuthService.mImpl
- .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators);
+ .canAuthenticate(TEST_OP_PACKAGE_NAME, mUserId, authenticators);
assertEquals(expectedResult, result);
waitForIdle();
verify(mBiometricService).canAuthenticate(
eq(TEST_OP_PACKAGE_NAME),
- eq(userId),
+ eq(mUserId),
eq(UserHandle.getCallingUserId()),
eq(authenticators));
}
@@ -439,18 +438,17 @@ public class AuthServiceTest {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
- final int userId = 0;
final boolean expectedResult = true;
when(mBiometricService.hasEnrolledBiometrics(anyInt(), anyString())).thenReturn(
expectedResult);
- final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(userId,
+ final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(mUserId,
TEST_OP_PACKAGE_NAME);
assertEquals(expectedResult, result);
waitForIdle();
verify(mBiometricService).hasEnrolledBiometrics(
- eq(userId),
+ eq(mUserId),
eq(TEST_OP_PACKAGE_NAME));
}
@@ -513,13 +511,12 @@ public class AuthServiceTest {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
- final int userId = 0;
final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
- mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators);
+ mAuthService.mImpl.getLastAuthenticationTime(mUserId, authenticators);
waitForIdle();
- verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators));
+ verify(mBiometricService).getLastAuthenticationTime(eq(mUserId), eq(authenticators));
}
private static void setInternalAndTestBiometricPermissions(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 2f7b8d26bdd9..4ef37b9cc3cf 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -69,6 +69,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.security.KeyStoreAuthorization;
import android.testing.TestableContext;
@@ -119,6 +120,7 @@ public class AuthSessionTest {
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
@Mock private BiometricCameraManager mBiometricCameraManager;
+ @Mock private UserManager mUserManager;
@Mock private BiometricManager mBiometricManager;
private Random mRandom;
@@ -846,7 +848,8 @@ public class AuthSessionTest {
TEST_PACKAGE,
checkDevicePolicyManager,
mContext,
- mBiometricCameraManager);
+ mBiometricCameraManager,
+ mUserManager);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index ddc35ed55901..8b6a4a22d691 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -62,6 +62,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -70,8 +71,16 @@ import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
import android.os.Handler;
@@ -84,6 +93,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.security.GateKeeper;
import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
@@ -93,6 +103,7 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -111,6 +122,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -144,6 +156,27 @@ public class BiometricServiceTest {
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
+ private final ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback.Stub>
+ mFingerprintAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+ IFingerprintAuthenticatorsRegisteredCallback.Stub.class);
+ private final ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback.Stub>
+ mFaceAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+ IFaceAuthenticatorsRegisteredCallback.Stub.class);
+ private final ArgumentCaptor<BiometricStateListener> mBiometricStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
+ private final List<FingerprintSensorPropertiesInternal>
+ mFingerprintSensorPropertiesInternals = List.of(
+ new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+ true /* resetLockoutRequiresHardwareAuthToken */));
+ private final List<FaceSensorPropertiesInternal>
+ mFaceSensorPropertiesInternals = List.of(
+ new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_UNKNOWN,
+ false /* supportsFaceDetection */, false /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */));
private BiometricService mBiometricService;
@@ -192,6 +225,10 @@ public class BiometricServiceTest {
@Mock
private BiometricNotificationLogger mNotificationLogger;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private FaceManager mFaceManager;
BiometricContextProvider mBiometricContextProvider;
@@ -1580,12 +1617,12 @@ public class BiometricServiceTest {
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
- invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
+ invokeCanAuthenticate(mBiometricService, Authenticators.IDENTITY_CHECK));
when(mTrustManager.isInSignificantPlace()).thenReturn(true);
- assertEquals(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE,
- invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE,
+ invokeCanAuthenticate(mBiometricService, Authenticators.IDENTITY_CHECK));
}
@Test
@@ -1603,13 +1640,13 @@ public class BiometricServiceTest {
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
- invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS
+ invokeCanAuthenticate(mBiometricService, Authenticators.IDENTITY_CHECK
| Authenticators.BIOMETRIC_STRONG));
when(mTrustManager.isInSignificantPlace()).thenReturn(true);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
- invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS
+ invokeCanAuthenticate(mBiometricService, Authenticators.IDENTITY_CHECK
| Authenticators.BIOMETRIC_STRONG));
}
@@ -1628,12 +1665,12 @@ public class BiometricServiceTest {
setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
- invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
+ invokeCanAuthenticate(mBiometricService, Authenticators.IDENTITY_CHECK));
when(mTrustManager.isInSignificantPlace()).thenReturn(true);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
- invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS
+ invokeCanAuthenticate(mBiometricService, Authenticators.IDENTITY_CHECK
| Authenticators.DEVICE_CREDENTIAL));
}
@@ -1964,6 +2001,59 @@ public class BiometricServiceTest {
eq(hardwareAuthenticators));
}
+ @Test
+ public void testMandatoryBiometricsValue_whenParentProfileEnabled() throws RemoteException {
+ final Context context = ApplicationProvider.getApplicationContext();
+ final int profileParentId = context.getContentResolver().getUserId();
+ final int userId = profileParentId + 1;
+ final BiometricService.SettingObserver settingObserver =
+ new BiometricService.SettingObserver(
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
+
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+ mFingerprintAuthenticatorRegisteredCallbackCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorRegisteredCallbackCaptor.capture());
+
+ mFingerprintAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFingerprintSensorPropertiesInternals);
+ mFaceAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFaceSensorPropertiesInternals);
+
+ verify(mFingerprintManager).registerBiometricStateListener(
+ mBiometricStateListenerArgumentCaptor.capture());
+
+ mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+ SENSOR_ID_FINGERPRINT, true /* hasEnrollments */);
+
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateListenerArgumentCaptor.capture());
+
+ mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+ SENSOR_ID_FACE, true /* hasEnrollments */);
+
+ when(mUserManager.getProfileParent(userId)).thenReturn(new UserInfo(profileParentId,
+ "", 0));
+ when(mUserManager.getEnabledProfileIds(profileParentId)).thenReturn(new int[]{userId});
+
+ //Disable Identity Check for profile user
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS, 0, userId);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 0,
+ userId);
+ //Enable Identity Check for parent user
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS, 1, profileParentId);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 1,
+ profileParentId);
+
+ assertTrue(settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ userId));
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index bf499c73a7fa..f6f831f41f83 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -40,6 +40,7 @@ import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.PromptInfo;
import android.os.RemoteException;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -66,6 +67,7 @@ public class PreAuthInfoTest {
DeviceFlagsValueProvider.createCheckFlagsRule();
private static final int USER_ID = 0;
+ private static final int OWNER_ID = 10;
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
@@ -86,6 +88,8 @@ public class PreAuthInfoTest {
BiometricService.SettingObserver mSettingObserver;
@Mock
BiometricCameraManager mBiometricCameraManager;
+ @Mock
+ UserManager mUserManager;
@Before
public void setup() throws RemoteException {
@@ -120,9 +124,9 @@ public class PreAuthInfoTest {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).isEmpty();
}
@@ -136,9 +140,9 @@ public class PreAuthInfoTest {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
@@ -153,9 +157,9 @@ public class PreAuthInfoTest {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
}
@@ -173,9 +177,9 @@ public class PreAuthInfoTest {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(faceSensor, fingerprintSensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(faceSensor, fingerprintSensor), USER_ID,
+ promptInfo, TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
@@ -193,9 +197,9 @@ public class PreAuthInfoTest {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(faceSensor, fingerprintSensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(faceSensor, fingerprintSensor), USER_ID,
+ promptInfo, TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FINGERPRINT);
@@ -209,10 +213,11 @@ public class PreAuthInfoTest {
final BiometricSensor sensor = getFaceSensor();
final PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
@@ -224,10 +229,11 @@ public class PreAuthInfoTest {
when(mTrustManager.isInSignificantPlace()).thenReturn(false);
final PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
}
@@ -240,11 +246,12 @@ public class PreAuthInfoTest {
final BiometricSensor sensor = getFaceSensor();
final PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK
| BiometricManager.Authenticators.BIOMETRIC_STRONG);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
@@ -257,13 +264,14 @@ public class PreAuthInfoTest {
final BiometricSensor sensor = getFaceSensor();
final PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
- BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
+ BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
}
@@ -281,9 +289,9 @@ public class PreAuthInfoTest {
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(faceSensor, fingerprintSensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(faceSensor, fingerprintSensor), USER_ID,
+ promptInfo, TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
@@ -298,15 +306,56 @@ public class PreAuthInfoTest {
final BiometricSensor sensor = getFaceSensor();
final PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(promptInfo.getNegativeButtonText()).isEqualTo(TEST_PACKAGE_NAME);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
+ public void testCredentialOwnerIdAsUserId() throws Exception {
+ when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
+
+ final BiometricSensor sensor = getFaceSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
+
+ assertThat(preAuthInfo.userId).isEqualTo(OWNER_ID);
+ assertThat(preAuthInfo.callingUserId).isEqualTo(USER_ID);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
+ public void testCredentialOwnerIdAsUserId_forMandatoryBiometrics() throws Exception {
+ when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
+ when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ OWNER_ID)).thenReturn(true);
+ when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ USER_ID)).thenReturn(false);
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+
+ final BiometricSensor sensor = getFaceSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
+ promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
+
+ assertThat(preAuthInfo.getIsMandatoryBiometricsAuthentication()).isTrue();
+ }
+
+ @Test
public void prioritizeStrengthErrorBeforeCameraUnavailableError() throws Exception {
final BiometricSensor sensor = getFaceSensorWithStrength(
BiometricManager.Authenticators.BIOMETRIC_WEAK);
@@ -315,7 +364,8 @@ public class PreAuthInfoTest {
promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index 1bea371c5786..c4167d23a6fb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -212,24 +212,24 @@ public class UtilsTest {
mContext, Authenticators.BIOMETRIC_MIN_STRENGTH));
assertThrows(SecurityException.class, () -> Utils.isValidAuthenticatorConfig(
- mContext, Authenticators.MANDATORY_BIOMETRICS));
+ mContext, Authenticators.IDENTITY_CHECK));
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
if (Flags.mandatoryBiometrics()) {
assertTrue(Utils.isValidAuthenticatorConfig(mContext,
- Authenticators.MANDATORY_BIOMETRICS));
+ Authenticators.IDENTITY_CHECK));
} else {
assertFalse(Utils.isValidAuthenticatorConfig(mContext,
- Authenticators.MANDATORY_BIOMETRICS));
+ Authenticators.IDENTITY_CHECK));
}
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
final int authenticator = 1 << i;
if (authenticator == Authenticators.DEVICE_CREDENTIAL
- || authenticator == Authenticators.MANDATORY_BIOMETRICS) {
+ || authenticator == Authenticators.IDENTITY_CHECK) {
continue;
}
assertFalse(Utils.isValidAuthenticatorConfig(mContext, 1 << i));
@@ -307,8 +307,8 @@ public class UtilsTest {
BiometricManager.BIOMETRIC_ERROR_LOCKOUT},
{BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
BiometricManager.BIOMETRIC_ERROR_LOCKOUT},
- {BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE,
- BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE}
+ {BiometricConstants.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE,
+ BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE}
};
for (int i = 0; i < testCases.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 8f23ab99ba7f..d7bfea867ef9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.log;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyFloat;
@@ -45,10 +47,14 @@ import com.android.server.biometrics.sensors.BaseClientMonitor;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
@Presubmit
@SmallTest
public class BiometricLoggerTest {
@@ -136,12 +142,53 @@ public class BiometricLoggerTest {
final int targetUserId = 4;
final long latency = 44;
final boolean enrollSuccessful = true;
+ final int templateId = 4;
- mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful, -1);
+ mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful, -1, templateId);
verify(mSink).enroll(
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT),
- eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat(), anyInt());
+ eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat(), anyInt(),
+ eq(templateId));
+ }
+
+ @Test
+ public void testUnEnroll() {
+ mLogger = createLogger();
+
+ final int targetUserId = 4;
+ final int reason = BiometricsProtoEnums.UNENROLL_REASON_DANGLING_FRAMEWORK;
+ final int templateId = 4;
+
+ mLogger.logOnUnEnrolled(targetUserId, reason, templateId);
+
+ verify(mSink).unenrolled(
+ eq(DEFAULT_MODALITY), eq(targetUserId), eq(reason), eq(templateId));
+ }
+
+ @Test
+ public void testEnumeration() {
+ mLogger = createLogger();
+
+ final int targetUserId = 4;
+ final int result = BiometricsProtoEnums.ENUMERATION_RESULT_OK;
+ final int[] templateIdsHal = {1, 2};
+ final int[] templateIdsFw = {2, 3};
+ mLogger.logOnEnumerated(targetUserId, result, templateIdsHal, templateIdsFw);
+
+ final ArgumentCaptor<int[]> captorHalIds = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> captorFwIds = ArgumentCaptor.forClass(int[].class);
+ verify(mSink).enumerated(
+ eq(DEFAULT_MODALITY), eq(targetUserId), eq(result), captorHalIds.capture(),
+ captorFwIds.capture());
+ assertThat(captorHalIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(1, 2);
+ assertThat(captorFwIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(2, 3);
}
@Test
@@ -193,7 +240,8 @@ public class BiometricLoggerTest {
mLogger.logOnEnrolled(2 /* targetUserId */,
10 /* latency */,
true /* enrollSuccessful */,
- 30 /* source */);
+ 30 /* source */,
+ 1 /* templateId */);
mLogger.logOnError(mContext, mOpContext,
4 /* error */,
0 /* vendorCode */,
@@ -207,7 +255,7 @@ public class BiometricLoggerTest {
anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat());
verify(mSink, never()).enroll(
anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat(),
- anyInt());
+ anyInt(), anyInt());
verify(mSink, never()).error(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
anyLong(), anyInt(), anyInt(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 4f07380dfb5a..26e2614be001 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -147,7 +147,7 @@ public class BiometricSchedulerOperationTest {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isTrue();
- assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isFalse();
+ assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isTrue();
}
@Test
@@ -201,7 +201,7 @@ public class BiometricSchedulerOperationTest {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
assertThat(mInterruptableOperation.start(mOnStartCallback)).isTrue();
- assertThat(mInterruptableOperation.start(mOnStartCallback)).isFalse();
+ assertThat(mInterruptableOperation.start(mOnStartCallback)).isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 90c07d489549..fd8c792f6bf8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.StubberKt.doReturn;
import android.content.Context;
import android.hardware.biometrics.AuthenticateOptions;
@@ -810,14 +811,16 @@ public class BiometricSchedulerTest {
final ClientMonitorCallback callback0 = mock(ClientMonitorCallback.class);
final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
+ final BiometricLogger logger = mock(BiometricLogger.class);
+ doReturn(logger).when(logger).swapAction(any(), anyInt());
final TestInternalCleanupClient client1 = new
TestInternalCleanupClient(mContext, daemon, userId,
- owner, TEST_SENSOR_ID, mock(BiometricLogger.class),
+ owner, TEST_SENSOR_ID, logger,
mBiometricContext, utils, authenticatorIds);
final TestInternalCleanupClient client2 = new
TestInternalCleanupClient(mContext, daemon, userId,
- owner, TEST_SENSOR_ID, mock(BiometricLogger.class),
+ owner, TEST_SENSOR_ID, logger,
mBiometricContext, utils, authenticatorIds);
//add initial start client to scheduler, so later clients will be on pending operation queue
@@ -1248,9 +1251,9 @@ public class BiometricSchedulerTest {
@Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
+ @NonNull Map<Integer, Long> authenticatorIds, int reason) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- logger, biometricContext, authenticatorIds);
+ logger, biometricContext, authenticatorIds, reason);
}
@Override
@@ -1289,10 +1292,10 @@ public class BiometricSchedulerTest {
Supplier<Object> lazyDaemon, IBinder token, int biometricId, int userId,
String owner, BiometricUtils<Fingerprint> utils, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Map<Integer, Long> authenticatorIds) {
+ Map<Integer, Long> authenticatorIds, int reason) {
return new TestRemovalClient(context, lazyDaemon, token, null,
new int[]{biometricId}, userId, owner, utils, sensorId, logger,
- biometricContext, authenticatorIds);
+ biometricContext, authenticatorIds, reason);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 6ac95c829f56..276da39615af 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -197,7 +197,7 @@ public class FaceEnrollClientTest {
client.onEnrollResult(new Face("face", 1 /* faceId */, 20 /* deviceId */), 0);
verify(mBiometricLogger).logOnEnrolled(anyInt(), anyLong(), anyBoolean(),
- eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW));
+ eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW), eq(1));
}
private FaceEnrollClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
index d8bdd50d8c08..734ee16e5404 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.IBinder;
@@ -44,12 +46,15 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
@Presubmit
@SmallTest
@@ -104,6 +109,8 @@ public class FaceInternalEnumerateClientTest {
mClient.onEnumerationResult(mFace, 0);
return null;
}).when(mSession).enumerateEnrollments();
+ final ArgumentCaptor<int[]> captorHalIds = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> captorFwIds = ArgumentCaptor.forClass(int[].class);
mClient.start(mCallback);
@@ -111,6 +118,17 @@ public class FaceInternalEnumerateClientTest {
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
assertThat(mNotificationSent).isFalse();
verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mBiometricLogger).logOnEnumerated(eq(USER_ID),
+ eq(BiometricsProtoEnums.ENUMERATION_RESULT_OK), captorHalIds.capture(),
+ captorFwIds.capture());
+ assertThat(captorHalIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(mBiometricId);
+ assertThat(captorFwIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(mBiometricId);
verify(mCallback).onClientFinished(mClient, true);
}
@@ -171,6 +189,8 @@ public class FaceInternalEnumerateClientTest {
mClient.onEnumerationResult(identifier, 0);
return null;
}).when(mSession).enumerateEnrollments();
+ final ArgumentCaptor<int[]> captorHalIds = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> captorFwIds = ArgumentCaptor.forClass(int[].class);
mClient.start(mCallback);
@@ -178,6 +198,17 @@ public class FaceInternalEnumerateClientTest {
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
assertThat(mNotificationSent).isTrue();
verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+ verify(mBiometricLogger).logOnEnumerated(eq(USER_ID),
+ eq(BiometricsProtoEnums.ENUMERATION_RESULT_DANGLING_BOTH), captorHalIds.capture(),
+ captorFwIds.capture());
+ assertThat(captorHalIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(2);
+ assertThat(captorFwIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(mBiometricId);
verify(mCallback).onClientFinished(mClient, true);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
index 1d9e9334f043..c224af2ff2ac 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.IBinder;
@@ -119,6 +120,6 @@ public class FaceRemovalClientTest {
return new FaceRemovalClient(mContext, () -> aidl, mToken,
mClientMonitorCallbackConverter, biometricIds, USER_ID,
"own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
- mAuthenticatorIds);
+ mAuthenticatorIds, BiometricsProtoEnums.UNENROLL_REASON_UNKNOWN);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 5c6513d19a5c..ea96d193c762 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -309,7 +309,7 @@ public class FingerprintEnrollClientTest {
client.onEnrollResult(new Fingerprint("fingerprint", 1 /* faceId */, 20 /* deviceId */), 0);
verify(mBiometricLogger).logOnEnrolled(anyInt(), anyLong(), anyBoolean(),
- eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW));
+ eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW), eq(1));
}
private void showHideOverlay(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index 7dcf8415f311..a6604d10067a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -18,6 +18,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -99,6 +100,7 @@ public class FingerprintInternalCleanupClientTest {
mAddedIds = new ArrayList<>();
when(mAidlSession.getSession()).thenReturn(mSession);
+ doReturn(mLogger).when(mLogger).swapAction(any(), anyInt());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
index fab120016434..a809c3b45fd2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
@@ -44,11 +46,13 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -108,6 +112,8 @@ public class FingerprintInternalEnumerateClientTest {
mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0);
return null;
}).when(mSession).enumerateEnrollments();
+ final ArgumentCaptor<int[]> captorHalIds = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> captorFwIds = ArgumentCaptor.forClass(int[].class);
mClient.start(mCallback);
verify(mSession).enumerateEnrollments();
@@ -116,6 +122,17 @@ public class FingerprintInternalEnumerateClientTest {
.collect(Collectors.toList())).containsExactly(2, 3);
assertThat(mNotificationSent).isTrue();
verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
+ verify(mBiometricLogger).logOnEnumerated(eq(USER_ID),
+ eq(BiometricsProtoEnums.ENUMERATION_RESULT_DANGLING_BOTH), captorHalIds.capture(),
+ captorFwIds.capture());
+ assertThat(captorHalIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(2, 3);
+ assertThat(captorFwIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(1);
verify(mCallback).onClientFinished(mClient, true);
}
@@ -125,12 +142,25 @@ public class FingerprintInternalEnumerateClientTest {
mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0);
return null;
}).when(mSession).enumerateEnrollments();
+ final ArgumentCaptor<int[]> captorHalIds = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> captorFwIds = ArgumentCaptor.forClass(int[].class);
mClient.start(mCallback);
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
assertThat(mNotificationSent).isFalse();
verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mBiometricLogger).logOnEnumerated(eq(USER_ID),
+ eq(BiometricsProtoEnums.ENUMERATION_RESULT_OK), captorHalIds.capture(),
+ captorFwIds.capture());
+ assertThat(captorHalIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(1);
+ assertThat(captorFwIds.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(1);
verify(mCallback).onClientFinished(mClient, true);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
index 64f07e20e958..ced916ea85d7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
@@ -91,7 +92,8 @@ public class FingerprintRemovalClientTest {
mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener,
mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID,
- mBiometricLogger, mBiometricContext, mAuthenticatorIds);
+ mBiometricLogger, mBiometricContext, mAuthenticatorIds,
+ BiometricsProtoEnums.UNENROLL_REASON_UNKNOWN);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 1a593dd9baba..42b7f4bcda7b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -755,6 +755,7 @@ public class GenericWindowPolicyControllerTest {
verify(mActivityListener, after(TIMEOUT_MILLIS).never())
.onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+ verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
verify(mActivityListener, never())
.onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
@@ -776,6 +777,10 @@ public class GenericWindowPolicyControllerTest {
.onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
verify(mActivityListener, after(TIMEOUT_MILLIS).never())
.onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+
+ assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
+
+ verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onSecureWindowHidden(eq(DISPLAY_ID));
}
@Test
@@ -794,6 +799,7 @@ public class GenericWindowPolicyControllerTest {
verify(mActivityListener, after(TIMEOUT_MILLIS).never())
.onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+ verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
verify(mActivityListener, never())
.onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
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 4d067f6bfc62..32578a7dc10f 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
@@ -50,7 +50,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.Manifest;
import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
@@ -65,6 +64,7 @@ import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
@@ -104,6 +104,7 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.WorkSource;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
@@ -113,10 +114,10 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
@@ -150,6 +151,7 @@ public class VirtualDeviceManagerServiceTest {
private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
private static final String PERMISSION_CONTROLLER_PACKAGE_NAME =
"com.android.permissioncontroller";
+ private static final String VIRTUAL_DEVICE_OWNER_PACKAGE = "com.android.virtualdevice.test";
private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
private static final String VENDING_PACKAGE_NAME = "com.android.vending";
private static final String GOOGLE_DIALER_PACKAGE_NAME = "com.google.android.dialer";
@@ -160,8 +162,8 @@ public class VirtualDeviceManagerServiceTest {
private static final int DISPLAY_ID_1 = 2;
private static final int DISPLAY_ID_2 = 3;
private static final int NON_EXISTENT_DISPLAY_ID = 42;
- private static final int DEVICE_OWNER_UID_1 = 50;
- private static final int DEVICE_OWNER_UID_2 = 51;
+ private static final int DEVICE_OWNER_UID_1 = Process.myUid();
+ private static final int DEVICE_OWNER_UID_2 = DEVICE_OWNER_UID_1 + 1;
private static final int UID_1 = 0;
private static final int UID_2 = 10;
private static final int UID_3 = 10000;
@@ -223,9 +225,7 @@ public class VirtualDeviceManagerServiceTest {
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
- public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -246,6 +246,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private IDisplayManager mIDisplayManager;
@Mock
+ private WindowManager mWindowManager;
+ @Mock
private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Mock
private DevicePolicyManager mDevicePolicyManagerMock;
@@ -296,11 +298,10 @@ public class VirtualDeviceManagerServiceTest {
private Intent createRestrictedActivityBlockedIntent(Set<String> displayCategories,
String targetDisplayCategory) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
- eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+ eq(VIRTUAL_DEVICE_OWNER_PACKAGE))).thenReturn(DISPLAY_ID_1);
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
420).setDisplayCategories(displayCategories).build();
- mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
- NONBLOCKED_APP_PACKAGE_NAME);
+ mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -385,8 +386,7 @@ public class VirtualDeviceManagerServiceTest {
// Allow virtual devices to be created on the looper thread for testing.
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
mInputController = new InputController(mNativeWrapperMock,
- new Handler(TestableLooper.get(this).getLooper()),
- mContext.getSystemService(WindowManager.class),
+ new Handler(TestableLooper.get(this).getLooper()), mWindowManager,
AttributionSource.myAttributionSource(), threadVerifier);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
@@ -537,7 +537,7 @@ public class VirtualDeviceManagerServiceTest {
.build();
mDeviceImpl.close();
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
GenericWindowPolicyController gwpc =
mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1);
@@ -545,6 +545,83 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void getDevicePolicy_customRecentsPolicy_untrustedDisplaygwpcShowsRecentsOnHostDevice() {
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM)
+ .build();
+ mDeviceImpl.close();
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+ GenericWindowPolicyController gwpc =
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1);
+ assertThat(gwpc.canShowTasksInHostDeviceRecents()).isTrue();
+ }
+
+ @Test
+ public void deviceOwner_cannotMessWithAnotherDeviceTheyDoNotOwn() {
+ VirtualDeviceImpl unownedDevice =
+ createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2);
+
+ // The arguments don't matter, the owner uid check is always the first statement.
+ assertThrows(SecurityException.class, () -> unownedDevice.goToSleep());
+ assertThrows(SecurityException.class, () -> unownedDevice.wakeUp());
+
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.launchPendingIntent(0, null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.registerIntentInterceptor(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.unregisterIntentInterceptor(null));
+
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.addActivityPolicyExemption(null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.removeActivityPolicyExemption(null));
+ assertThrows(SecurityException.class, () -> unownedDevice.setDevicePolicy(0, 0));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.setDevicePolicyForDisplay(0, 0, 0));
+ assertThrows(SecurityException.class, () -> unownedDevice.setDisplayImePolicy(0, 0));
+
+ assertThrows(SecurityException.class, () -> unownedDevice.registerVirtualCamera(null));
+ assertThrows(SecurityException.class, () -> unownedDevice.unregisterVirtualCamera(null));
+
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.onAudioSessionStarting(0, null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.onAudioSessionEnded());
+
+ assertThrows(SecurityException.class, () -> unownedDevice.createVirtualDisplay(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.createVirtualDpad(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.createVirtualMouse(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.createVirtualTouchscreen(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.createVirtualNavigationTouchpad(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.createVirtualStylus(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.createVirtualRotaryEncoder(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.unregisterInputDevice(null));
+
+ assertThrows(SecurityException.class, () -> unownedDevice.sendDpadKeyEvent(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.sendKeyEvent(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.sendButtonEvent(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.sendTouchEvent(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.sendRelativeEvent(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.sendScrollEvent(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.sendStylusMotionEvent(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.sendStylusButtonEvent(null, null));
+ assertThrows(SecurityException.class,
+ () -> unownedDevice.sendRotaryEncoderScrollEvent(null, null));
+ assertThrows(SecurityException.class, () -> unownedDevice.setShowPointerIcon(true));
+
+ assertThrows(SecurityException.class, () -> unownedDevice.getVirtualSensorList());
+ assertThrows(SecurityException.class, () -> unownedDevice.sendSensorEvent(null, null));
+ }
+
+ @Test
public void getDeviceOwnerUid_oneDevice_returnsCorrectId() {
int ownerUid = mLocalService.getDeviceOwnerUid(mDeviceImpl.getDeviceId());
assertThat(ownerUid).isEqualTo(mDeviceImpl.getOwnerUid());
@@ -662,7 +739,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
- DEVICE_OWNER_UID_2);
+ DEVICE_OWNER_UID_1);
addVirtualDisplay(secondDevice, DISPLAY_ID_2);
secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
@@ -677,7 +754,7 @@ public class VirtualDeviceManagerServiceTest {
public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
- DEVICE_OWNER_UID_2);
+ DEVICE_OWNER_UID_1);
addVirtualDisplay(secondDevice, DISPLAY_ID_2);
@@ -694,7 +771,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -715,7 +792,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void getPreferredLocaleListForApp_appOnMultipleVD_localeOnFirstVDReturned() {
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
- DEVICE_OWNER_UID_2);
+ DEVICE_OWNER_UID_1);
Binder secondBinder = new Binder("secondBinder");
VirtualKeyboardConfig firstKeyboardConfig =
new VirtualKeyboardConfig.Builder()
@@ -734,8 +811,8 @@ public class VirtualDeviceManagerServiceTest {
.setLanguageTag("fr-FR")
.build();
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -753,7 +830,7 @@ public class VirtualDeviceManagerServiceTest {
assertThat(mCameraAccessController.getObserverCount()).isEqualTo(1);
VirtualDeviceImpl secondDevice =
- createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2);
+ createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_1);
assertThat(mCameraAccessController.getObserverCount()).isEqualTo(2);
mDeviceImpl.close();
@@ -912,20 +989,35 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
- public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
+ public void onVirtualDisplayCreatedLocked_notTrustedDisplay_noWakeLockIsAcquired()
+ throws RemoteException {
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), anyInt(), eq(null));
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ TestableLooper.get(this).processAllMessages();
+ verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
+ nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+ nullable(String.class), anyInt(), eq(null));
+ }
+
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
+ verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
+ nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+ nullable(String.class), anyInt(), eq(null));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
assertThrows(IllegalStateException.class,
() -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
TestableLooper.get(this).processAllMessages();
@@ -934,9 +1026,10 @@ public class VirtualDeviceManagerServiceTest {
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -949,9 +1042,10 @@ public class VirtualDeviceManagerServiceTest {
verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -972,24 +1066,52 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualDpad_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualKeyboard_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
}
@Test
+ public void createVirtualKeyboard_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualMouse_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
}
@Test
+ public void createVirtualMouse_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
}
@Test
+ public void createVirtualTouchscreen_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
assertThrows(IllegalArgumentException.class,
() -> new VirtualTouchscreenConfig.Builder(
@@ -1005,7 +1127,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
VirtualTouchscreenConfig positiveConfig =
new VirtualTouchscreenConfig.Builder(
/* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -1028,6 +1150,14 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualNavigationTouchpad_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER));
+ }
+
+ @Test
public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() {
assertThrows(IllegalArgumentException.class,
() -> new VirtualNavigationTouchpadConfig.Builder(
@@ -1043,7 +1173,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
VirtualNavigationTouchpadConfig positiveConfig =
new VirtualNavigationTouchpadConfig.Builder(
/* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -1067,71 +1197,8 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
- public void createVirtualDpad_noPermission_failsSecurityException() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
- }
- }
-
- @Test
- public void createVirtualKeyboard_noPermission_failsSecurityException() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
- }
- }
-
- @Test
- public void createVirtualMouse_noPermission_failsSecurityException() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
- }
- }
-
- @Test
- public void createVirtualTouchscreen_noPermission_failsSecurityException() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
- }
- }
-
- @Test
- public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
- BINDER));
- }
- }
-
- @Test
- public void onAudioSessionStarting_noPermission_failsSecurityException() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.onAudioSessionStarting(
- DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
- }
- }
-
- @Test
- public void onAudioSessionEnded_noPermission_failsSecurityException() {
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
- }
- }
-
- @Test
public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
assertWithMessage("Virtual dpad should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1141,7 +1208,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1151,7 +1218,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -1172,7 +1239,7 @@ public class VirtualDeviceManagerServiceTest {
.setAssociatedDisplayId(DISPLAY_ID_1)
.build();
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -1193,7 +1260,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
assertWithMessage("Virtual mouse should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1203,7 +1270,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1213,7 +1280,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
.that(
@@ -1473,9 +1540,9 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void setShowPointerIcon_setsValueForAllDisplays() {
- addVirtualDisplay(mDeviceImpl, 1);
- addVirtualDisplay(mDeviceImpl, 2);
- addVirtualDisplay(mDeviceImpl, 3);
+ addVirtualDisplay(mDeviceImpl, 1, Display.FLAG_TRUSTED);
+ addVirtualDisplay(mDeviceImpl, 2, Display.FLAG_TRUSTED);
+ addVirtualDisplay(mDeviceImpl, 3, Display.FLAG_TRUSTED);
VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
.setAssociatedDisplayId(1)
.setInputDeviceName(DEVICE_NAME_1)
@@ -1508,6 +1575,14 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void setShowPointerIcon_untrustedDisplay_pointerIconIsAlwaysShown() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ clearInvocations(mInputManagerInternalMock);
+ mDeviceImpl.setShowPointerIcon(false);
+ verify(mInputManagerInternalMock, times(0)).setPointerIconVisible(eq(false), anyInt());
+ }
+
+ @Test
public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
@@ -1950,7 +2025,7 @@ public class VirtualDeviceManagerServiceTest {
mVirtualDeviceLog,
new Binder(),
new AttributionSource(
- ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
+ ownerUid, VIRTUAL_DEVICE_OWNER_PACKAGE, "virtualdevice"),
virtualDeviceId,
mInputController,
mCameraAccessController,
@@ -1969,16 +2044,20 @@ public class VirtualDeviceManagerServiceTest {
}
private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+ addVirtualDisplay(virtualDevice, displayId, /* flags= */ 0);
+ }
+
+ private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId, int flags) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
eq(virtualDevice), any(), any())).thenReturn(displayId);
- virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
- NONBLOCKED_APP_PACKAGE_NAME);
final String uniqueId = UNIQUE_ID + displayId;
doAnswer(inv -> {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = uniqueId;
+ displayInfo.flags = flags;
return displayInfo;
}).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+ virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback);
mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
}
@@ -1997,23 +2076,10 @@ public class VirtualDeviceManagerServiceTest {
private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
CharSequence displayName) {
return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
- /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
+ MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
- /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
- }
-
- /** Helper class to drop permissions temporarily and restore them at the end of a test. */
- static final class DropShellPermissionsTemporarily implements AutoCloseable {
- DropShellPermissionsTemporarily() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .dropShellPermissionIdentity();
- }
-
- @Override
- public void close() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity();
- }
+ /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
+ /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null, /* deviceId= */ null);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 51c2ad1d1134..687a1ab4c7aa 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -200,7 +200,9 @@ public class VirtualAudioControllerTest {
AudioPlaybackConfiguration audioPlaybackConfiguration =
new AudioPlaybackConfiguration(
playerIdCard, /* piid= */ 1000, appUid, /* pid= */ 1000);
- audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, /* deviceId= */1);
+ int[] deviceIds = new int[1];
+ deviceIds[0] = 1;
+ audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, deviceIds);
configs.add(audioPlaybackConfiguration);
}
return configs;
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index e09d2225d101..95d601f69344 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -34,6 +35,11 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Build;
+import android.os.Process;
+
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.os.PermissionEnforcer;
import android.permission.PermissionCheckerManager;
@@ -46,6 +52,7 @@ import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.server.LocalServices;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -58,6 +65,8 @@ import java.util.Set;
public class PlatformCompatTest {
private static final String PACKAGE_NAME = "my.package";
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -460,4 +469,79 @@ public class PlatformCompatTest {
assertThat(mPlatformCompat.isChangeEnabled(3L, systemAppInfo)).isTrue();
verify(mChangeReporter).reportChange(123, 3L, ChangeReporter.STATE_ENABLED, true, false);
}
+
+ @DisableFlags(Flags.FLAG_SYSTEM_UID_TARGET_SYSTEM_SDK)
+ @Test
+ public void testSharedSystemUidFlagOff() throws Exception {
+ testSharedSystemUid(false);
+ }
+
+ @EnableFlags(Flags.FLAG_SYSTEM_UID_TARGET_SYSTEM_SDK)
+ @Test
+ public void testSharedSystemUidFlagOn() throws Exception {
+ testSharedSystemUid(true);
+ }
+
+ private void testSharedSystemUid(Boolean expectSystemUidTargetSystemSdk) throws Exception {
+ final String systemUidPackageNameTargetsR = "systemuid.package1";
+ final String systemUidPackageNameTargetsQ = "systemuid.package2";
+ final String nonSystemUidPackageNameTargetsR = "nonsystemuid.package1";
+ final String nonSystemUidPackageNameTargetsQ = "nonsystemuid.package2";
+ final int nonSystemUid = 123;
+
+ mCompatConfig =
+ CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addEnableSinceSdkChangeWithId(Build.VERSION_CODES.R, 1L)
+ .build();
+ mCompatConfig.forceNonDebuggableFinalForTest(true);
+ mPlatformCompat =
+ new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter);
+
+ ApplicationInfo systemUidAppInfo1 = ApplicationInfoBuilder.create()
+ .withPackageName(systemUidPackageNameTargetsR)
+ .withUid(Process.SYSTEM_UID)
+ .withTargetSdk(Build.VERSION_CODES.R)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(systemUidPackageNameTargetsR), anyLong(), anyInt(), anyInt()))
+ .thenReturn(systemUidAppInfo1);
+
+ ApplicationInfo systemUidAppInfo2 = ApplicationInfoBuilder.create()
+ .withPackageName(systemUidPackageNameTargetsQ)
+ .withUid(Process.SYSTEM_UID)
+ .withTargetSdk(Build.VERSION_CODES.Q)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(systemUidPackageNameTargetsQ), anyLong(), anyInt(), anyInt()))
+ .thenReturn(systemUidAppInfo2);
+
+ ApplicationInfo nonSystemUidAppInfo1 = ApplicationInfoBuilder.create()
+ .withPackageName(nonSystemUidPackageNameTargetsR)
+ .withUid(nonSystemUid)
+ .withTargetSdk(Build.VERSION_CODES.R)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(nonSystemUidPackageNameTargetsR), anyLong(), anyInt(), anyInt()))
+ .thenReturn(nonSystemUidAppInfo1);
+
+ ApplicationInfo nonSystemUidAppInfo2 = ApplicationInfoBuilder.create()
+ .withPackageName(nonSystemUidPackageNameTargetsQ)
+ .withUid(nonSystemUid)
+ .withTargetSdk(Build.VERSION_CODES.Q)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(nonSystemUidPackageNameTargetsQ), anyLong(), anyInt(), anyInt()))
+ .thenReturn(nonSystemUidAppInfo2);
+
+ when(mPackageManager.getPackagesForUid(eq(Process.SYSTEM_UID)))
+ .thenReturn(new String[] {systemUidPackageNameTargetsR, systemUidPackageNameTargetsQ});
+ when(mPackageManager.getPackagesForUid(eq(nonSystemUid)))
+ .thenReturn(new String[] {
+ nonSystemUidPackageNameTargetsR, nonSystemUidPackageNameTargetsQ
+ });
+
+ assertThat(mPlatformCompat.isChangeEnabledByUid(1L, Process.SYSTEM_UID))
+ .isEqualTo(expectSystemUidTargetSystemSdk);
+ assertThat(mPlatformCompat.isChangeEnabledByUid(1L, nonSystemUid)).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 698bda335f83..4c381eb4429e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -24,6 +24,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicyManagerLiteInternal;
import android.app.backup.IBackupManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Intent;
@@ -488,6 +489,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
public Context createContextAsUser(UserHandle user) {
return context;
}
+
+ @Override
+ SupervisionManagerInternal getSupervisionManager() {
+ return services.supervisionManagerInternal;
+ }
}
static class TransferOwnershipMetadataManagerMockInjector extends
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index cb4269a205e4..30aa8cebdff6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -109,6 +109,7 @@ import android.app.admin.PasswordMetrics;
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.Flags;
import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -134,6 +135,10 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.KeyChain;
@@ -165,6 +170,7 @@ import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
@@ -207,6 +213,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
public static final String INVALID_CALLING_IDENTITY_MSG = "Calling identity is not authorized";
public static final String ONGOING_CALL_MSG = "ongoing call on the device";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
// TODO replace all instances of this with explicit {@link #mServiceContext}.
@Deprecated
private DpmMockContext mContext;
@@ -4395,6 +4404,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeEnabledModifiesSetting() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -4406,6 +4416,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeEnabledWithPOOnUser0() throws Exception {
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
setupProfileOwnerOnUser0();
@@ -4425,6 +4436,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -4902,6 +4914,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_profileOwner() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4930,6 +4943,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_deviceOwner() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -4948,6 +4962,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_nonOwner() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4964,6 +4979,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_nonSupervisionApp() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4996,6 +5012,53 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
+ public void testIsSecondaryLockscreenEnabled() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ verifyIsSecondaryLockscreenEnabled(false);
+ verifyIsSecondaryLockscreenEnabled(true);
+ }
+
+ private void verifyIsSecondaryLockscreenEnabled(boolean expected) throws Exception {
+ reset(getServices().supervisionManagerInternal);
+
+ doReturn(expected).when(getServices().supervisionManagerInternal)
+ .isSupervisionLockscreenEnabledForUser(anyInt());
+
+ final boolean enabled = dpm.isSecondaryLockscreenEnabled(UserHandle.of(CALLER_USER_HANDLE));
+ verify(getServices().supervisionManagerInternal)
+ .isSupervisionLockscreenEnabledForUser(CALLER_USER_HANDLE);
+
+ assertThat(enabled).isEqualTo(expected);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
+ public void testSetSecondaryLockscreenEnabled() throws Exception {
+ verifySetSecondaryLockscreenEnabled(false);
+ verifySetSecondaryLockscreenEnabled(true);
+ }
+
+ private void verifySetSecondaryLockscreenEnabled(boolean enabled) throws Exception {
+ reset(getServices().supervisionManagerInternal);
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ doReturn(DpmMockContext.CALLER_UID).when(getServices().packageManagerInternal)
+ .getPackageUid(any(), anyLong(), anyInt());
+
+ dpm.setSecondaryLockscreenEnabled(admin1, enabled);
+ verify(getServices().supervisionManagerInternal).setSupervisionLockscreenEnabledForUser(
+ CALLER_USER_HANDLE, enabled, null);
+
+ reset(getServices().supervisionManagerInternal);
+
+ dpm.setSecondaryLockscreenEnabled(enabled, new PersistableBundle());
+ verify(getServices().supervisionManagerInternal).setSupervisionLockscreenEnabledForUser(
+ eq(CALLER_USER_HANDLE), eq(enabled), any(PersistableBundle.class));
+ }
+
+ @Test
public void testIsDeviceManaged() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 2e200a9268f5..3e4448c1dafa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -35,6 +35,7 @@ import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.IBackupManager;
import android.app.role.RoleManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -77,8 +78,8 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.AlarmManagerInternal;
-import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.pkg.PackageState;
@@ -149,6 +150,7 @@ public class MockSystemServices {
public final BuildMock buildMock = new BuildMock();
public final File dataDir;
public final PolicyPathProvider pathProvider;
+ public final SupervisionManagerInternal supervisionManagerInternal;
private final Map<String, PackageState> mTestPackageStates = new ArrayMap<>();
@@ -203,6 +205,7 @@ public class MockSystemServices {
roleManager = realContext.getSystemService(RoleManager.class);
roleManagerForMock = mock(RoleManagerForMock.class);
subscriptionManager = mock(SubscriptionManager.class);
+ supervisionManagerInternal = mock(SupervisionManagerInternal.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 733f0565fd83..c6df146dd42a 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -22,15 +22,18 @@ import static com.android.compatibility.common.util.PollingCheck.waitFor;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
-import android.annotation.NonNull;
+import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateRequest;
@@ -38,17 +41,22 @@ import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
-import androidx.test.InstrumentationRegistry;
+import androidx.annotation.NonNull;
import androidx.test.filters.FlakyTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
+import com.android.window.flags.Flags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,18 +69,30 @@ import java.util.Optional;
import javax.annotation.Nullable;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
/**
* Unit tests for {@link DeviceStateManagerService}.
- * <p/>
- * Run with <code>atest DeviceStateManagerServiceTest</code>.
+ *
+ * <p> Build/Install/Run:
+ * atest FrameworksServicesTests:DeviceStateManagerServiceTest
*/
@Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public final class DeviceStateManagerServiceTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
new DeviceState.Configuration.Builder(0, "DEFAULT").build());
+ private static final int DEFAULT_DEVICE_STATE_IDENTIFIER = DEFAULT_DEVICE_STATE.getIdentifier();
+ private static final String DEFAULT_DEVICE_STATE_TRACE_STRING =
+ DEFAULT_DEVICE_STATE_IDENTIFIER + ":" + DEFAULT_DEVICE_STATE.getName();
+
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
new DeviceState.Configuration.Builder(1, "DEFAULT").build());
+ private static final int OTHER_DEVICE_STATE_IDENTIFIER = OTHER_DEVICE_STATE.getIdentifier();
+ private static final String OTHER_DEVICE_STATE_TRACE_STRING =
+ OTHER_DEVICE_STATE_IDENTIFIER + ":" + OTHER_DEVICE_STATE.getName();
+
private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
new DeviceState(new DeviceState.Configuration.Builder(2,
"DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP")
@@ -93,24 +113,43 @@ public final class DeviceStateManagerServiceTest {
private static final int TIMEOUT = 2000;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(Flags.FLAG_WLINFO_ONCREATE);
+ }
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ @NonNull
private TestDeviceStatePolicy mPolicy;
+ @NonNull
private TestDeviceStateProvider mProvider;
+ @NonNull
private DeviceStateManagerService mService;
+ @NonNull
private TestSystemPropertySetter mSysPropSetter;
+ @NonNull
private WindowProcessController mWindowProcessController;
+ public DeviceStateManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(flags);
+ }
+
@Before
public void setup() {
mProvider = new TestDeviceStateProvider();
- mPolicy = new TestDeviceStatePolicy(mProvider);
+ mPolicy = new TestDeviceStatePolicy(mContext, mProvider);
mSysPropSetter = new TestSystemPropertySetter();
setupDeviceStateManagerService();
- flushHandler(); // Flush the handler to ensure the initial values are committed.
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+ }
}
private void setupDeviceStateManagerService() {
- mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy,
- mSysPropSetter);
+ mService = new DeviceStateManagerService(mContext, mPolicy, mSysPropSetter);
// Necessary to allow us to check for top app process id in tests
mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
@@ -136,56 +175,53 @@ public final class DeviceStateManagerServiceTest {
@Test
public void baseStateChanged() {
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
-
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
+
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
}
@Test
public void baseStateChanged_withStatePendingPolicyCallback() {
mPolicy.blockConfigure();
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
-
- mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
+
+ mProvider.setState(DEFAULT_DEVICE_STATE_IDENTIFIER);
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
mPolicy.resumeConfigure();
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -194,13 +230,12 @@ public final class DeviceStateManagerServiceTest {
mProvider.setState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
});
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -208,25 +243,23 @@ public final class DeviceStateManagerServiceTest {
assertThrows(IllegalArgumentException.class,
() -> mProvider.setState(INVALID_DEVICE_STATE_IDENTIFIER));
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
public void supportedStatesChanged() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
@@ -235,30 +268,31 @@ public final class DeviceStateManagerServiceTest {
// The current committed and requests states do not change because the current state remains
// supported.
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().supportedStates, List.of(DEFAULT_DEVICE_STATE));
+ assertThat(callback.getLastNotifiedInfo().supportedStates)
+ .containsExactly(DEFAULT_DEVICE_STATE);
}
@Test
public void supportedStatesChanged_statesRemainSame() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- // An initial callback will be triggered on registration, so we clear it here.
- flushHandler();
- callback.clearLastNotifiedInfo();
+ if (!Flags.wlinfoOncreate()) {
+ // An initial callback will be triggered on registration, so we clear it here.
+ flushHandler();
+ callback.clearLastNotifiedInfo();
+ }
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
@@ -268,26 +302,26 @@ public final class DeviceStateManagerServiceTest {
// The current committed and requests states do not change because the current state remains
// supported.
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
// The callback wasn't notified about a change in supported states as the states have not
// changed.
- assertNull(callback.getLastNotifiedInfo());
+ assertThat(callback.getLastNotifiedInfo()).isNull();
}
@Test
public void getDeviceStateInfo() throws RemoteException {
- DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
- assertNotNull(info);
- assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
- assertEquals(info.baseState, DEFAULT_DEVICE_STATE);
- assertEquals(info.currentState, DEFAULT_DEVICE_STATE);
+ final DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
+ assertThat(info).isNotNull();
+ assertThat(info.supportedStates)
+ .containsExactlyElementsIn(SUPPORTED_DEVICE_STATES).inOrder();
+ assertThat(info.baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(info.currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@FlakyTest(bugId = 297949293)
@@ -295,105 +329,176 @@ public final class DeviceStateManagerServiceTest {
public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException {
// Create a provider and a service without an initial base state.
mProvider = new TestDeviceStateProvider(null /* initialState */);
- mPolicy = new TestDeviceStatePolicy(mProvider);
+ mPolicy = new TestDeviceStatePolicy(mContext, mProvider);
setupDeviceStateManagerService();
- flushHandler(); // Flush the handler to ensure the initial values are committed.
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+ }
- DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
+ final DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
- assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
- assertEquals(info.baseState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
- assertEquals(info.currentState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
+ assertThat(info.supportedStates)
+ .containsExactlyElementsIn(SUPPORTED_DEVICE_STATES).inOrder();
+ assertThat(info.baseState.getIdentifier()).isEqualTo(INVALID_DEVICE_STATE_IDENTIFIER);
+ assertThat(info.currentState.getIdentifier()).isEqualTo(INVALID_DEVICE_STATE_IDENTIFIER);
+ }
+
+ @Test
+ public void halRegisterUnregisterCallback() throws RemoteException {
+ IDeviceStateService halService = mService.getHalBinderService();
+ IDeviceStateListener halListener = new IDeviceStateListener.Stub() {
+ @Override
+ public void onDeviceStateChanged(DeviceStateConfiguration deviceState) { }
+
+ @Override
+ public int getInterfaceVersion() {
+ return IDeviceStateListener.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return IDeviceStateListener.HASH;
+ }
+ };
+
+ int errorCode = ErrorCode.OK;
+ try {
+ halService.unregisterListener(halListener);
+ } catch(ServiceSpecificException e) {
+ errorCode = e.errorCode;
+ }
+ assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+ errorCode = ErrorCode.OK;
+ try {
+ halService.unregisterListener(null);
+ } catch(ServiceSpecificException e) {
+ errorCode = e.errorCode;
+ }
+ assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+ halService.registerListener(halListener);
+
+ errorCode = ErrorCode.OK;
+ try {
+ halService.registerListener(halListener);
+ } catch (ServiceSpecificException e) {
+ errorCode = e.errorCode;
+ }
+ assertEquals(errorCode, ErrorCode.ALREADY_EXISTS);
+
+ halService.unregisterListener(halListener);
}
@Test
public void registerCallback() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
+ if (Flags.wlinfoOncreate()) {
+ waitAndAssert(() -> callback.getLastNotifiedInfo() != null);
+ }
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
- mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+ mProvider.setState(DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
-
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
mPolicy.blockConfigure();
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
// The callback should not have been notified of the state change as the policy is still
// pending callback.
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
mPolicy.resumeConfigure();
// Now that the policy is finished processing the callback should be notified of the state
// change.
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
}
@Test
- public void registerCallback_emitsInitialValue() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
- mService.getBinderService().registerCallback(callback);
- flushHandler();
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
+ public void registerCallback_initialValueAvailable_emitsDeviceState() throws RemoteException {
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+
+ final DeviceStateInfo stateInfo;
+ if (Flags.wlinfoOncreate()) {
+ stateInfo = mService.getBinderService().registerCallback(callback);
+ } else {
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+ stateInfo = callback.getLastNotifiedInfo();
+ }
+
+ assertThat(stateInfo).isNotNull();
+ assertThat(stateInfo.baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(stateInfo.currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@Test
- public void registerCallback_initialValueUnavailable() throws RemoteException {
+ public void registerCallback_initialValueUnavailable_nullDeviceState() throws RemoteException {
// Create a provider and a service without an initial base state.
mProvider = new TestDeviceStateProvider(null /* initialState */);
- mPolicy = new TestDeviceStatePolicy(mProvider);
+ mPolicy = new TestDeviceStatePolicy(mContext, mProvider);
setupDeviceStateManagerService();
- flushHandler(); // Flush the handler to ensure the initial values are committed.
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+ }
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
- mService.getBinderService().registerCallback(callback);
- flushHandler();
- // The callback should never be called when the base state is not set yet.
- assertNull(callback.getLastNotifiedInfo());
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final DeviceStateInfo stateInfo;
+ if (Flags.wlinfoOncreate()) {
+ // Return null when the base state is not set yet.
+ stateInfo = mService.getBinderService().registerCallback(callback);
+ } else {
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+ // The callback should never be called when the base state is not set yet.
+ stateInfo = callback.getLastNotifiedInfo();
+ }
+
+ assertThat(stateInfo).isNull();
}
@Test
public void requestState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, OTHER_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
waitAndAssert(() -> callback.getLastNotifiedStatus(token)
== TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
-
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
+
+ assertThat(callback.getLastNotifiedInfo()).isNotNull();
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(OTHER_DEVICE_STATE);
mService.getBinderService().cancelStateRequest();
@@ -401,156 +506,156 @@ public final class DeviceStateManagerServiceTest {
== TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(DEFAULT_DEVICE_STATE)));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
-
- assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
+
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@FlakyTest(bugId = 200332057)
@Test
public void requestState_pendingStateAtRequest() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
mPolicy.blockConfigure();
final IBinder firstRequestToken = new Binder();
final IBinder secondRequestToken = new Binder();
- assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(firstRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(secondRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(firstRequestToken,
- OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ mService.getBinderService()
+ .requestState(firstRequestToken, OTHER_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- mService.getBinderService().requestState(secondRequestToken,
- DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ mService.getBinderService()
+ .requestState(secondRequestToken, DEFAULT_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
mPolicy.resumeConfigureOnce();
flushHandler();
// First request status is now canceled as there is another pending request.
- assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(firstRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
// Second request status still unknown because the service is still awaiting policy
// callback.
- assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(callback.getLastNotifiedStatus(secondRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
mPolicy.resumeConfigure();
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
// Now cancel the second request to make the first request active.
mService.getBinderService().cancelStateRequest();
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
- assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
-
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(callback.getLastNotifiedStatus(firstRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(secondRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
+
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
public void requestState_sameAsBaseState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, DEFAULT_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
}
@Test
public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
+ mService.getBinderService().requestState(token, OTHER_DEVICE_STATE_IDENTIFIER,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
-
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getOverrideState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
+
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
flushHandler();
// Request is canceled because the base state changed.
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -581,8 +686,8 @@ public final class DeviceStateManagerServiceTest {
requestState_flagCancelWhenRequesterNotOnTop_common(
// When the app is foreground, the state should not change
() -> {
- int pid = Binder.getCallingPid();
- int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
try {
mService.mProcessObserver.onForegroundActivitiesChanged(pid, uid,
true /* foregroundActivities */);
@@ -594,8 +699,8 @@ public final class DeviceStateManagerServiceTest {
() -> {
when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
try {
- int pid = Binder.getCallingPid();
- int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
mService.mProcessObserver.onForegroundActivitiesChanged(pid, uid,
false /* foregroundActivities */);
@@ -609,68 +714,68 @@ public final class DeviceStateManagerServiceTest {
@FlakyTest(bugId = 200332057)
@Test
public void requestState_becomesUnsupported() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, OTHER_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
mProvider.notifySupportedDeviceStates(
new DeviceState[]{DEFAULT_DEVICE_STATE});
flushHandler();
// Request is canceled because the state is no longer supported.
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state as the override state is no longer
// supported.
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
public void requestState_unsupportedState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token,
- UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
});
}
@Test
public void requestState_invalidState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token, INVALID_DEVICE_STATE_IDENTIFIER,
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, INVALID_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
});
}
@@ -678,40 +783,41 @@ public final class DeviceStateManagerServiceTest {
public void requestState_beforeRegisteringCallback() {
assertThrows(IllegalStateException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, DEFAULT_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
});
}
@Test
public void requestBaseStateOverride() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestBaseStateOverride(token,
- OTHER_DEVICE_STATE.getIdentifier(),
+ OTHER_DEVICE_STATE_IDENTIFIER,
0 /* flags */);
waitAndAssert(() -> callback.getLastNotifiedStatus(token)
== TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
-
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
+
+ assertThat(callback.getLastNotifiedInfo()).isNotNull();
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(OTHER_DEVICE_STATE);
mService.getBinderService().cancelBaseStateOverride();
@@ -719,52 +825,50 @@ public final class DeviceStateManagerServiceTest {
== TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(DEFAULT_DEVICE_STATE)));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertFalse(mService.getOverrideBaseState().isPresent());
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideBaseState()).isEmpty();
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@Test
public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
- final DeviceState testDeviceState = new DeviceState(new DeviceState.Configuration.Builder(2,
- "TEST").build());
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final DeviceState testDeviceState = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "TEST").build());
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, testDeviceState});
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestBaseStateOverride(token,
- OTHER_DEVICE_STATE.getIdentifier(),
+ OTHER_DEVICE_STATE_IDENTIFIER,
0 /* flags */);
waitAndAssert(() -> callback.getLastNotifiedStatus(token)
== TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
-
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
+
+ assertThat(callback.getLastNotifiedInfo()).isNotNull();
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(OTHER_DEVICE_STATE);
mProvider.setState(testDeviceState.getIdentifier());
@@ -772,22 +876,22 @@ public final class DeviceStateManagerServiceTest {
== TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set to the new base state once the override is cleared.
waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(testDeviceState)));
- assertEquals(mSysPropSetter.getValue(),
- testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
- assertEquals(mService.getBaseState(), Optional.of(testDeviceState));
- assertFalse(mService.getOverrideBaseState().isPresent());
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- testDeviceState.getIdentifier());
+ assertThat(mSysPropSetter.getValue())
+ .isEqualTo(testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
+ assertThat(mService.getBaseState()).hasValue(testDeviceState);
+ assertThat(mService.getOverrideBaseState()).isEmpty();
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(testDeviceState.getIdentifier());
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== testDeviceState.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState, testDeviceState);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(testDeviceState);
}
@Test
public void requestBaseState_unsupportedState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
@@ -799,7 +903,7 @@ public final class DeviceStateManagerServiceTest {
@Test
public void requestBaseState_invalidState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
@@ -814,11 +918,46 @@ public final class DeviceStateManagerServiceTest {
assertThrows(IllegalStateException.class, () -> {
final IBinder token = new Binder();
mService.getBinderService().requestBaseStateOverride(token,
- DEFAULT_DEVICE_STATE.getIdentifier(),
+ DEFAULT_DEVICE_STATE_IDENTIFIER,
0 /* flags */);
});
}
+ @Test
+ public void shouldShowRdmEduDialog1() {
+ // RDM V1 Cases
+ assertTrue(DeviceStateManagerService.shouldShowRdmEduDialog(
+ false /* hasControlDeviceStatePermission */,
+ false /* requestingRdmOuterDefault */,
+ false /* isDeviceClosed (no-op) */));
+
+ assertFalse(DeviceStateManagerService.shouldShowRdmEduDialog(
+ true /* hasControlDeviceStatePermission */,
+ false /* requestingRdmOuterDefault */,
+ true /* isDeviceClosed (no-op) */));
+
+ // RDM V2 Cases
+ // hasControlDeviceStatePermission = false
+ assertFalse(DeviceStateManagerService.shouldShowRdmEduDialog(
+ false /* hasControlDeviceStatePermission */,
+ true /* requestingRdmOuterDefault */,
+ false /* isDeviceClosed */));
+ assertTrue(DeviceStateManagerService.shouldShowRdmEduDialog(
+ false /* hasControlDeviceStatePermission */,
+ true /* requestingRdmOuterDefault */,
+ true /* isDeviceClosed */));
+
+ // hasControlDeviceStatePermission = true
+ assertFalse(DeviceStateManagerService.shouldShowRdmEduDialog(
+ true /* hasControlDeviceStatePermission */,
+ true /* requestingRdmOuterDefault */,
+ false /* isDeviceClosed */));
+ assertFalse(DeviceStateManagerService.shouldShowRdmEduDialog(
+ true /* hasControlDeviceStatePermission */,
+ true /* requestingRdmOuterDefault */,
+ true /* isDeviceClosed */));
+ }
+
/**
* Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag.
*
@@ -834,21 +973,23 @@ public final class DeviceStateManagerServiceTest {
Runnable noChangeEvent,
Runnable autoCancelEvent
) throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- flushHandler();
+ if (!Flags.wlinfoOncreate()) {
+ flushHandler();
+ }
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestState(token,
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier(),
0 /* flags */);
flushHandler(2 /* count */);
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertDeviceStateConditions(
@@ -858,8 +999,8 @@ public final class DeviceStateManagerServiceTest {
noChangeEvent.run();
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
assertDeviceStateConditions(
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
DEFAULT_DEVICE_STATE, /* base state */
@@ -867,8 +1008,8 @@ public final class DeviceStateManagerServiceTest {
autoCancelEvent.run();
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
assertDeviceStateConditions(DEFAULT_DEVICE_STATE, DEFAULT_DEVICE_STATE,
false /* isOverrideState */);
}
@@ -881,20 +1022,20 @@ public final class DeviceStateManagerServiceTest {
* @param isOverrideState whether a state override is active.
*/
private void assertDeviceStateConditions(
- DeviceState state, DeviceState baseState,
+ @NonNull DeviceState state, @NonNull DeviceState baseState,
boolean isOverrideState) {
- assertEquals(mService.getCommittedState(), Optional.of(state));
- assertEquals(mService.getBaseState(), Optional.of(baseState));
- assertEquals(mSysPropSetter.getValue(),
- state.getIdentifier() + ":" + state.getName());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- state.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(state);
+ assertThat(mService.getBaseState()).hasValue(baseState);
+ assertThat(mSysPropSetter.getValue())
+ .isEqualTo(state.getIdentifier() + ":" + state.getName());
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(state.getIdentifier());
if (isOverrideState) {
// When a state override is active, the committed state should batch the override state.
- assertEquals(mService.getOverrideState().get(), state);
+ assertThat(mService.getOverrideState()).hasValue(state);
} else {
// When there is no state override, the override state should be empty.
- assertFalse(mService.getOverrideState().isPresent());
+ assertThat(mService.getOverrideState()).isEmpty();
}
}
@@ -902,10 +1043,11 @@ public final class DeviceStateManagerServiceTest {
private final DeviceStateProvider mProvider;
private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE_IDENTIFIER;
private boolean mConfigureBlocked = false;
+ @Nullable
private Runnable mPendingConfigureCompleteRunnable;
- TestDeviceStatePolicy(DeviceStateProvider provider) {
- super(InstrumentationRegistry.getContext());
+ TestDeviceStatePolicy(@NonNull Context context, @NonNull DeviceStateProvider provider) {
+ super(context);
mProvider = provider;
}
@@ -921,7 +1063,7 @@ public final class DeviceStateManagerServiceTest {
public void resumeConfigure() {
mConfigureBlocked = false;
if (mPendingConfigureCompleteRunnable != null) {
- Runnable onComplete = mPendingConfigureCompleteRunnable;
+ final Runnable onComplete = mPendingConfigureCompleteRunnable;
mPendingConfigureCompleteRunnable = null;
onComplete.run();
}
@@ -929,7 +1071,7 @@ public final class DeviceStateManagerServiceTest {
public void resumeConfigureOnce() {
if (mPendingConfigureCompleteRunnable != null) {
- Runnable onComplete = mPendingConfigureCompleteRunnable;
+ final Runnable onComplete = mPendingConfigureCompleteRunnable;
mPendingConfigureCompleteRunnable = null;
onComplete.run();
}
@@ -940,7 +1082,7 @@ public final class DeviceStateManagerServiceTest {
}
@Override
- public void configureDeviceForState(int state, Runnable onComplete) {
+ public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
if (mPendingConfigureCompleteRunnable != null) {
throw new IllegalStateException("configureDeviceForState() called while configure"
+ " is pending");
@@ -966,7 +1108,9 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE,
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
- @Nullable private final DeviceState mInitialState;
+ @Nullable
+ private final DeviceState mInitialState;
+ @Nullable
private Listener mListener;
private TestDeviceStateProvider() {
@@ -991,7 +1135,7 @@ public final class DeviceStateManagerServiceTest {
}
}
- public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
+ public void notifySupportedDeviceStates(@NonNull DeviceState[] supportedDeviceStates) {
mSupportedDeviceStates = supportedDeviceStates;
mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
@@ -1018,17 +1162,17 @@ public final class DeviceStateManagerServiceTest {
private final HashMap<IBinder, Integer> mLastNotifiedStatus = new HashMap<>();
@Override
- public void onDeviceStateInfoChanged(DeviceStateInfo info) {
+ public void onDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
mLastNotifiedInfo = info;
}
@Override
- public void onRequestActive(IBinder token) {
+ public void onRequestActive(@NonNull IBinder token) {
mLastNotifiedStatus.put(token, STATUS_ACTIVE);
}
@Override
- public void onRequestCanceled(IBinder token) {
+ public void onRequestCanceled(@NonNull IBinder token) {
mLastNotifiedStatus.put(token, STATUS_CANCELED);
}
@@ -1041,20 +1185,22 @@ public final class DeviceStateManagerServiceTest {
mLastNotifiedInfo = null;
}
- int getLastNotifiedStatus(IBinder requestToken) {
+ int getLastNotifiedStatus(@NonNull IBinder requestToken) {
return mLastNotifiedStatus.getOrDefault(requestToken, STATUS_UNKNOWN);
}
}
private static final class TestSystemPropertySetter implements
DeviceStateManagerService.SystemPropertySetter {
+ @NonNull
private String mValue;
@Override
- public void setDebugTracingDeviceStateProperty(String value) {
+ public void setDebugTracingDeviceStateProperty(@NonNull String value) {
mValue = value;
}
+ @NonNull
public String getValue() {
return mValue;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java
index 04f921f495a2..629f9683a547 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java
@@ -26,6 +26,7 @@ final class FakePowerManagerWrapper extends PowerManagerWrapper {
private boolean mInteractive;
private WakeLockWrapper mWakeLock;
private boolean mWasWakeLockInstanceCreated = false;
+ private boolean mIsLowPowerStandbyEnabled = false;
FakePowerManagerWrapper(@NonNull Context context) {
@@ -60,6 +61,15 @@ final class FakePowerManagerWrapper extends PowerManagerWrapper {
}
@Override
+ boolean isLowPowerStandbyEnabled() {
+ return mIsLowPowerStandbyEnabled;
+ }
+
+ void setIsLowPowerStandbyEnabled(boolean isLowPowerStandbyEnabled) {
+ mIsLowPowerStandbyEnabled = isLowPowerStandbyEnabled;
+ }
+
+ @Override
WakeLockWrapper newWakeLock(int levelAndFlags, String tag) {
if (mWakeLock == null) {
mWakeLock = new FakeWakeLockWrapper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index c7574bdc3f6c..30dac9f3813d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -41,11 +41,14 @@ import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.tv.flags.Flags;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.stats.hdmi.HdmiStatsEnums;
import androidx.test.InstrumentationRegistry;
@@ -54,6 +57,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.SystemService;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -83,6 +87,9 @@ public class HdmiCecAtomLoggingTest {
private HdmiEarcController mHdmiEarcController;
private FakeEarcNativeWrapper mEarcNativeWrapper;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws RemoteException {
mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter());
@@ -232,7 +239,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.VOLUME_MUTE,
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
- HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -258,7 +266,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
- HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -285,7 +294,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
Constants.MESSAGE_RECORD_ON,
- HdmiStatsEnums.UNRECOGNIZED_OPCODE);
+ HdmiStatsEnums.UNRECOGNIZED_OPCODE,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -311,7 +321,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
- HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -337,6 +348,59 @@ public class HdmiCecAtomLoggingTest {
}
@Test
+ @EnableFlags({Flags.FLAG_HDMI_CONTROL_COLLECT_PHYSICAL_ADDRESS})
+ public void testMessageReported_writesAtom_reportPhysicalAddress() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_PLAYBACK_1, 0x1234, HdmiDeviceInfo.DEVICE_PLAYBACK);
+
+ mHdmiCecAtomWriterSpy.messageReported(
+ message,
+ HdmiStatsEnums.INCOMING,
+ 1234);
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .writeHdmiCecMessageReportedAtom(
+ 1234,
+ HdmiStatsEnums.INCOMING,
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
+ HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
+ HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ 0x1234);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_HDMI_CONTROL_COLLECT_PHYSICAL_ADDRESS})
+ public void testMessageReported_writesAtom_reportPhysicalAddress_noParams() {
+ HdmiCecMessage message = HdmiCecMessage.build(
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ new byte[0]);
+
+ mHdmiCecAtomWriterSpy.messageReported(
+ message,
+ HdmiStatsEnums.INCOMING,
+ 1234);
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .writeHdmiCecMessageReportedAtom(
+ 1234,
+ HdmiStatsEnums.INCOMING,
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
+ HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
+ HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
+ }
+
+ @Test
public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
@@ -369,4 +433,21 @@ public class HdmiCecAtomLoggingTest {
.dsmStatusChanged(anyBoolean(), anyBoolean(),
eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
}
+
+ @Test
+ public void testPowerStateChangeOnActiveSourceLostToggled_writesAtom_logReasonSetting() {
+ mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.writePowerStateChangeOnActiveSourceLostAtom(true);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .powerStateChangeOnActiveSourceLostChanged(eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING), anyString(), anyInt(), anyInt());
+ verify(mHdmiCecAtomWriterSpy, never())
+ .powerStateChangeOnActiveSourceLostChanged(eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP), anyString(), anyInt(), anyInt());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index a0005d968e31..861e72d4ac79 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -37,6 +37,7 @@ import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -94,9 +95,6 @@ public class HdmiCecLocalDevicePlaybackTest {
private boolean mActiveMediaSessionsPaused;
private FakePowerManagerInternalWrapper mPowerManagerInternal =
new FakePowerManagerInternalWrapper();
-
- private boolean mIsOnActiveSourceLostPopupActive;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -164,12 +162,12 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@Override
void startHdmiCecActiveSourceLostActivity() {
- mIsOnActiveSourceLostPopupActive = true;
+ setIsActiveSourceLostPopupLaunched(true);
}
@Override
void dismissUiOnActiveSourceStatusRecovered() {
- mIsOnActiveSourceLostPopupActive = false;
+ setIsActiveSourceLostPopupLaunched(false);
}
};
mHdmiCecLocalDevicePlayback.init();
@@ -413,7 +411,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -422,9 +421,8 @@ public class HdmiCecLocalDevicePlaybackTest {
int newPlaybackPhysicalAddress = 0x2100;
int switchPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(newPlaybackPhysicalAddress);
- mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
-
+ mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
@@ -457,7 +455,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -617,7 +616,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -722,7 +722,8 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
ADDR_INVALID);
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS,true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -1265,14 +1266,35 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
-
- // After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ // After 30s of device inactivity, device would assert active source.
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
}
@Test
+ public void handleActiveSourceFromTv_tvNotAnswerRequest_assertActiveSource() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ mPowerManager.setInteractive(true);
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
+ message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+ // After 30s of device inactivity, device would assert active source.
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ false);
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ }
+
+ @Test
public void handleActiveSource_otherDevice_ActiveSource_mediaSessionsPaused() {
mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
@@ -1343,7 +1365,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
// 3. DUT becomes <AS> again.
@@ -1704,9 +1727,9 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
-
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -2323,11 +2346,197 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@Test
+ public void onActiveSourceLostToTv_requestActiveSourceAnsweredFromTv_showPopup() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
+ }
+
+ @Test
+ public void onActiveSourceLostToTv_requestActiveSourceNotAnswered_assertActiveSource() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is not shown, playback device asserts active source since TV doesn't answer the
+ // request.
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
+ assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(mPlaybackLogicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(mPlaybackPhysicalAddress);
+ }
+
+ @Test
+ public void onActiveSourceLost_requestActiveSourceNotAnswered_playbackIsAS_dontShowPopup() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+ HdmiCecMessage setStreamPathToPlayback = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+ mPlaybackPhysicalAddress);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is not shown since playback device is active source.
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
+ assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(mPlaybackLogicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(mPlaybackPhysicalAddress);
+ }
+
+ @Test
+ public void onActiveSourceLost_requestASNotAnswered_setStreamPathToNonCecInput_dontShowPopup() {
+ int otherPhysicalAddress = mPlaybackPhysicalAddress + 0x0100;
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+ HdmiCecMessage setStreamPathToOtherInput = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+ otherPhysicalAddress);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToOtherInput))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is shown, playback device doesn't assert active source since active path is
+ // switched to a non-CEC device.
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(ADDR_INVALID);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(otherPhysicalAddress);
+ }
+
+ @Test
public void onActiveSourceLost_interactionWithDut_noStandbyAfterTimeout() {
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -2350,8 +2559,14 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
// User interacted with the DUT, so the device will not go to standby.
- skipActiveSourceLostUi(0);
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
@@ -2387,13 +2602,17 @@ public class HdmiCecLocalDevicePlaybackTest {
// Pop-up is triggered.
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ // RequestActiveSourceAction is triggered and TV confirms active source.
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2407,6 +2626,8 @@ public class HdmiCecLocalDevicePlaybackTest {
@Test
public void onActiveSourceLost_incomingRoutingChangeToDut_noStandbyAfterTimeout() {
+ int otherPlaybackLogicalAddress = mPlaybackLogicalAddress == Constants.ADDR_PLAYBACK_2
+ ? Constants.ADDR_PLAYBACK_1 : Constants.ADDR_PLAYBACK_2;
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
@@ -2420,30 +2641,87 @@ public class HdmiCecLocalDevicePlaybackTest {
mNativeWrapper.clearResultMessages();
mTestLooper.dispatchAll();
- HdmiCecMessage activeSourceFromTv =
- HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage activeSourceFromOtherPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(otherPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress + 0x0100);
HdmiCecMessage activeSourceFromPlayback =
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
HdmiCecMessage routingChangeToPlayback =
- HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV,
+ mPlaybackPhysicalAddress + 0x0100,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromOtherPlayback))
.isEqualTo(Constants.HANDLED);
- assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ otherPlaybackLogicalAddress);
mTestLooper.dispatchAll();
// Pop-up is triggered.
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(routingChangeToPlayback))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
+ assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(mPlaybackLogicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(mPlaybackPhysicalAddress);
+ mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ }
+
+ @Test
+ public void onActiveSourceLost_oneTouchPlay_noStandbyAfterTimeout() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is triggered.
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+ // RequestActiveSourceAction is triggered and TV confirms active source.
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback() {
+ @Override
+ public void onComplete(int result) throws RemoteException {
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2600,13 +2878,30 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mPowerManager.isInteractive()).isFalse();
}
- private void skipActiveSourceLostUi(long idleDuration) {
+ private void skipActiveSourceLostUi(long idleDuration, boolean activeSourceLostToTv,
+ boolean tvAnswersRequest) {
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ if (activeSourceLostToTv) {
+ // RequestActiveSourceAction is triggered.
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ if (tvAnswersRequest) {
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV,
+ 0x0000);
+ mNativeWrapper.onCecMessage(activeSource);
+ mTestLooper.dispatchAll();
+ } else {
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
+ return;
+ }
+ }
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
mPowerManagerInternal.setIdleDuration(idleDuration);
mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
}
-}
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 2d957401e6bd..5be4490e67ef 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -28,7 +28,7 @@ import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
-import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS;
+import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS;
import static com.android.server.hdmi.RoutingControlAction.TIMEOUT_ROUTING_INFORMATION_MS;
import static com.android.server.hdmi.RequestSadAction.RETRY_COUNTER_MAX;
@@ -70,6 +70,7 @@ import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
@SmallTest
@@ -113,6 +114,11 @@ public class HdmiCecLocalDeviceTvTest {
private boolean mWokenUp;
private boolean mEarcBlocksArc;
private List<DeviceEventListener> mDeviceEventListeners = new ArrayList<>();
+ private List<VendorCommandListener> mVendorCommandListeners = new ArrayList<>();
+ private boolean mDisableCecOnStandbyByLowEnergyMode;
+ private boolean mWasCecDisabledOnStandbyByLowEnergyMode;
+ private boolean mUseHdmiCecPowerStatusController;
+ private boolean mUserEnabledCecInOfflineMode;
private class DeviceEventListener {
private HdmiDeviceInfo mDevice;
@@ -132,6 +138,30 @@ public class HdmiCecLocalDeviceTvTest {
}
}
+ private class VendorCommandListener {
+ private boolean mEnabled;
+ private int mReason;
+
+ VendorCommandListener(boolean enabled, int reason) {
+ this.mEnabled = enabled;
+ this.mReason = reason;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof VendorCommandListener)) {
+ return false;
+ }
+ VendorCommandListener other = (VendorCommandListener) obj;
+ return other.mReason == mReason && other.mEnabled == mEnabled;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEnabled, mReason);
+ }
+ }
+
private FakeAudioFramework mAudioFramework;
private AudioManagerWrapper mAudioManager;
@@ -169,6 +199,9 @@ public class HdmiCecLocalDeviceTvTest {
@Override
boolean isPowerStandby() {
+ if (mUseHdmiCecPowerStatusController) {
+ return mPowerStatusController.isPowerStatusStandby();
+ }
return false;
}
@@ -188,6 +221,13 @@ public class HdmiCecLocalDeviceTvTest {
}
@Override
+ boolean invokeVendorCommandListenersOnControlStateChanged(
+ boolean enabled, int reason) {
+ mVendorCommandListeners.add(new VendorCommandListener(enabled, reason));
+ return true;
+ }
+
+ @Override
protected boolean earcBlocksArcConnection() {
return mEarcBlocksArc;
}
@@ -196,6 +236,26 @@ public class HdmiCecLocalDeviceTvTest {
protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
// do nothing
}
+
+ @Override
+ protected boolean getDisableCecOnStandbyByLowEnergyMode() {
+ return mDisableCecOnStandbyByLowEnergyMode;
+ }
+
+ @Override
+ protected boolean getWasCecDisabledOnStandbyByLowEnergyMode() {
+ return mWasCecDisabledOnStandbyByLowEnergyMode;
+ }
+
+ @Override
+ protected void setWasCecDisabledOnStandbyByLowEnergyMode(boolean value) {
+ mWasCecDisabledOnStandbyByLowEnergyMode = value;
+ }
+
+ @Override
+ protected boolean userEnabledCecInOfflineMode() {
+ return mUserEnabledCecInOfflineMode;
+ }
};
mHdmiControlService.setIoLooper(mMyLooper);
@@ -241,6 +301,10 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService.getHdmiCecConfig().setIntValue(
sad, HdmiControlManager.QUERY_SAD_DISABLED);
}
+ mWasCecDisabledOnStandbyByLowEnergyMode = false;
+ mDisableCecOnStandbyByLowEnergyMode = false;
+ mUseHdmiCecPowerStatusController = false;
+ mUserEnabledCecInOfflineMode = false;
mNativeWrapper.clearResultMessages();
}
@@ -1820,7 +1884,7 @@ public class HdmiCecLocalDeviceTvTest {
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1853,7 +1917,7 @@ public class HdmiCecLocalDeviceTvTest {
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1889,7 +1953,7 @@ public class HdmiCecLocalDeviceTvTest {
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1932,7 +1996,7 @@ public class HdmiCecLocalDeviceTvTest {
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1969,7 +2033,7 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService.sendCecCommand(setStreamPathFromTv);
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
@@ -2177,6 +2241,25 @@ public class HdmiCecLocalDeviceTvTest {
}
@Test
+ public void handleReportPhysicalAddress_samePathAsActiveSource_differentLA_newActiveSource() {
+ // This scenario can be reproduced if active source is hotplugged out and replaced with
+ // another device that might have another LA.
+ int physicalAddress = 0x1000;
+ mHdmiControlService.setActiveSource(Constants.ADDR_PLAYBACK_1, physicalAddress,
+ "HdmiControlServiceTest");
+ mHdmiCecLocalDeviceTv.setActivePath(physicalAddress);
+ HdmiCecMessage reportPhysicalAddressFromPlayback2 =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(ADDR_PLAYBACK_2,
+ physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+ physicalAddress);
+ mNativeWrapper.onCecMessage(reportPhysicalAddressFromPlayback2);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+ }
+
+ @Test
public void onOneTouchPlay_wakeUp_addCecDevice() {
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
.isEmpty();
@@ -2238,6 +2321,118 @@ public class HdmiCecLocalDeviceTvTest {
assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).hasSize(1);
}
+ @Test
+ public void lowEnergyMode_disableCecOnStandby_reEnableOnWakeup() {
+ mDisableCecOnStandbyByLowEnergyMode = true;
+ mUseHdmiCecPowerStatusController = true;
+ mPowerManager.setIsLowPowerStandbyEnabled(true);
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertTrue(mWasCecDisabledOnStandbyByLowEnergyMode);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+ }
+
+ @Test
+ public void lowEnergyMode_disableCecBeforeStandby_cecStaysDisabledOnWakeup() {
+ mDisableCecOnStandbyByLowEnergyMode = true;
+ mUseHdmiCecPowerStatusController = true;
+ mPowerManager.setIsLowPowerStandbyEnabled(true);
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ }
+
+ @Test
+ public void lowEnergyMode_onWakeUp_reEnableCec_invokeVendorCommandListeners() {
+ mDisableCecOnStandbyByLowEnergyMode = true;
+ mUseHdmiCecPowerStatusController = true;
+ mPowerManager.setIsLowPowerStandbyEnabled(true);
+ VendorCommandListener vendorCommandListenerInvocationWakeup = new VendorCommandListener(
+ true, HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP);
+ VendorCommandListener vendorCommandListenerInvocationSettingChange =
+ new VendorCommandListener(true,
+ HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP);
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertTrue(mWasCecDisabledOnStandbyByLowEnergyMode);
+ mVendorCommandListeners.clear();
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertThat(mVendorCommandListeners.size()).isEqualTo(2);
+ assertTrue(mVendorCommandListeners.contains(vendorCommandListenerInvocationWakeup));
+ assertTrue(mVendorCommandListeners.contains(vendorCommandListenerInvocationSettingChange));
+ }
+
+ @Test
+ public void lowEnergyMode_userEnabledCecInOfflineMode_onStandby_cecStaysEnabled() {
+ mDisableCecOnStandbyByLowEnergyMode = true;
+ mUseHdmiCecPowerStatusController = true;
+ mUserEnabledCecInOfflineMode = true;
+ mPowerManager.setIsLowPowerStandbyEnabled(true);
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+ }
+
protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
MockTvDevice(HdmiControlService service) {
super(service);
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
index 41cb6fd99d9b..e1dcc99d3851 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
@@ -128,8 +128,6 @@ public class ContextHubEventLoggerTest {
}
@Test
- @EnableFlags({Flags.FLAG_RELIABLE_MESSAGE,
- Flags.FLAG_RELIABLE_MESSAGE_IMPLEMENTATION})
public void testLogReliableMessageToNanoappStatus() {
NanoAppMessage message1 = NanoAppMessage.createMessageToNanoApp(1, 0,
new byte[] {0x00, 0x11, 0x22, 0x33});
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index 685e8d6a3bc5..e611867493eb 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -65,7 +65,7 @@ public class ContextHubServiceTest {
new Pair<>(Arrays.asList(mMockContextHubInfo), Arrays.asList(""));
when(mMockContextHubInfo.getId()).thenReturn(CONTEXT_HUB_ID);
when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
- when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
+ when(mMockContextHubWrapper.getContextHubs()).thenReturn(hubInfo);
when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index c6e2427ce66b..3ced56a04138 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -25,6 +25,7 @@ import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.ContentRecordingSession.TARGET_UID_FULL_SCREEN;
import static android.view.ContentRecordingSession.TARGET_UID_UNKNOWN;
import static android.view.ContentRecordingSession.createDisplaySession;
@@ -82,9 +83,11 @@ import android.os.test.TestLooper;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.testing.TestableContext;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
+import android.view.Display;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
@@ -348,30 +351,86 @@ public class MediaProjectionManagerServiceTest {
.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testCreateProjection_keyguardLocked_RoleHeld() {
- runWithRole(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, () -> {
- try {
- mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
- doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
- any(ApplicationInfoFlags.class), any(UserHandle.class));
- MediaProjectionManagerService.MediaProjection projection =
- mService.createProjectionInternal(Process.myUid(),
- mContext.getPackageName(),
- TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
- doReturn(true).when(mKeyguardManager).isKeyguardLocked();
- doReturn(PackageManager.PERMISSION_DENIED).when(
- mPackageManager).checkPermission(
- RECORD_SENSITIVE_CONTENT, projection.packageName);
-
- projection.start(mIMediaProjectionCallback);
- projection.notifyVirtualDisplayCreated(10);
-
- // The projection was started because it was allowed to capture the keyguard.
- assertWithMessage("Failed to run projection")
- .that(mService.getActiveProjectionInfo()).isNotNull();
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
+ runWithRole(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ () -> {
+ try {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ doReturn(mAppInfo)
+ .when(mPackageManager)
+ .getApplicationInfoAsUser(
+ anyString(),
+ any(ApplicationInfoFlags.class),
+ any(UserHandle.class));
+ MediaProjectionManagerService.MediaProjection projection =
+ mService.createProjectionInternal(
+ Process.myUid(),
+ mContext.getPackageName(),
+ TYPE_MIRRORING,
+ /* isPermanentGrant= */ false,
+ UserHandle.CURRENT,
+ DEFAULT_DISPLAY);
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mPackageManager)
+ .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the
+ // keyguard.
+ assertWithMessage("Failed to run projection")
+ .that(mService.getActiveProjectionInfo())
+ .isNotNull();
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_screenshareProtectionsDisabled()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ int value = Settings.Global.getInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0);
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
+ } finally {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
+ }
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_noDisplayCreated()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
@Test
@@ -436,8 +495,13 @@ public class MediaProjectionManagerServiceTest {
// We are allowed to create another projection.
MediaProjectionManagerService.MediaProjection secondProjection =
- mService.createProjectionInternal(UID + 10, PACKAGE_NAME + "foo",
- TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT);
+ mService.createProjectionInternal(
+ UID + 10,
+ PACKAGE_NAME + "foo",
+ TYPE_MIRRORING,
+ /* isPermanentGrant= */ true,
+ UserHandle.CURRENT,
+ Display.DEFAULT_DISPLAY);
assertThat(secondProjection).isNotNull();
@@ -470,6 +534,8 @@ public class MediaProjectionManagerServiceTest {
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
projection.start(mIMediaProjectionCallback);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
doReturn(true).when(mKeyguardManager).isKeyguardLocked();
MediaProjectionManagerService.BinderService mediaProjectionBinderService =
mService.new BinderService(mContext);
@@ -478,49 +544,6 @@ public class MediaProjectionManagerServiceTest {
verify(mContext, never()).startActivityAsUser(any(), any());
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
- @Test
- public void testKeyguardLocked_stopsActiveProjection() throws Exception {
- MediaProjectionManagerService service =
- new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
- MediaProjectionManagerService.MediaProjection projection =
- startProjectionPreconditions(service);
- projection.start(mIMediaProjectionCallback);
-
- assertThat(service.getActiveProjectionInfo()).isNotNull();
-
- doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
- .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
- service.onKeyguardLockedStateChanged(true);
-
- verify(mMediaProjectionMetricsLogger)
- .logStopped(UID, TARGET_UID_UNKNOWN, StopReason.STOP_DEVICE_LOCKED);
- assertThat(service.getActiveProjectionInfo()).isNull();
- assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
- }
-
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
- @Test
- public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection()
- throws NameNotFoundException {
- MediaProjectionManagerService service =
- new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
- MediaProjectionManagerService.MediaProjection projection =
- startProjectionPreconditions(service);
- projection.start(mIMediaProjectionCallback);
-
- assertThat(service.getActiveProjectionInfo()).isNotNull();
-
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
- RECORD_SENSITIVE_CONTENT, projection.packageName);
- service.onKeyguardLockedStateChanged(true);
-
- verifyZeroInteractions(mMediaProjectionMetricsLogger);
- assertThat(service.getActiveProjectionInfo()).isNotNull();
- }
-
@Test
public void stop_noActiveProjections_doesNotLog() throws Exception {
MediaProjectionManagerService service =
@@ -644,7 +667,7 @@ public class MediaProjectionManagerServiceTest {
mClockInjector);
MediaProjectionManagerService.MediaProjection projection = createProjectionPreconditions(
service);
- mClock.fastForward(projection.mDefaultTimeoutMs + 10);
+ mClock.fastForward(projection.mDefaultTimeoutMillis + 10);
// Immediate timeout - so no longer valid.
assertThat(projection.isValid()).isFalse();
@@ -1222,6 +1245,13 @@ public class MediaProjectionManagerServiceTest {
verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
}
+ @Test
+ public void createProjectionForSecondaryDisplay() throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection =
+ createProjectionPreconditions(mService, 200);
+ assertThat(projection.getDisplayId()).isEqualTo(200);
+ }
+
private void verifySetSessionWithContent(@RecordContent int content) {
verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
mSessionCaptor.capture());
@@ -1231,12 +1261,21 @@ public class MediaProjectionManagerServiceTest {
// Set up preconditions for creating a projection.
private MediaProjectionManagerService.MediaProjection createProjectionPreconditions(
- MediaProjectionManagerService service)
- throws NameNotFoundException {
+ MediaProjectionManagerService service) throws NameNotFoundException {
+ return createProjectionPreconditions(service, Display.DEFAULT_DISPLAY);
+ }
+
+ private MediaProjectionManagerService.MediaProjection createProjectionPreconditions(
+ MediaProjectionManagerService service, int displayId) throws NameNotFoundException {
doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
any(ApplicationInfoFlags.class), any(UserHandle.class));
- return service.createProjectionInternal(UID, PACKAGE_NAME,
- TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
+ return service.createProjectionInternal(
+ UID,
+ PACKAGE_NAME,
+ TYPE_MIRRORING,
+ /* isPermanentGrant= */ false,
+ UserHandle.CURRENT,
+ displayId);
}
// Set up preconditions for starting a projection, with no foreground service requirements.
@@ -1280,7 +1319,8 @@ public class MediaProjectionManagerServiceTest {
Manifest.permission.BYPASS_ROLE_QUALIFICATION);
roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
+ roleManager.addRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
mContext.getMainExecutor(), success -> {
if (success) {
latch.countDown();
@@ -1295,9 +1335,9 @@ public class MediaProjectionManagerServiceTest {
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
- roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
- mContext.getMainExecutor(), (aBool) -> {
- });
+ roleManager.removeRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), (aBool) -> {});
roleManager.setBypassingRoleQualification(false);
instrumentation.getUiAutomation()
.dropShellPermissionIdentity();
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
new file mode 100644
index 000000000000..affcfc14034e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
+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.NameNotFoundException;
+import android.media.projection.MediaProjectionManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.testing.TestableContext;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for the {@link MediaProjectionStopController} class.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:MediaProjectionStopControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"})
+public class MediaProjectionStopControllerTest {
+ private static final int UID = 10;
+ private static final String PACKAGE_NAME = "test.package";
+ private final ApplicationInfo mAppInfo = new ApplicationInfo();
+ @Rule
+ public final TestableContext mContext = spy(
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()));
+
+ private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+ return mMediaProjectionMetricsLogger;
+ }
+ };
+
+ private MediaProjectionManagerService mService;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ private ActivityManagerInternal mAmInternal;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
+ private TelecomManager mTelecomManager;
+
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private Consumer<Integer> mStopConsumer;
+
+ private MediaProjectionStopController mStopController;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
+
+ mAppOpsManager = mockAppOpsManager();
+ mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+ mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
+ mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
+ mContext.setMockPackageManager(mPackageManager);
+
+ mStopController = new MediaProjectionStopController(mContext, mStopConsumer);
+ mService = new MediaProjectionManagerService(mContext,
+ mMediaProjectionMetricsLoggerInjector);
+
+ mAppInfo.targetSdkVersion = 35;
+ }
+
+ private static AppOpsManager mockAppOpsManager() {
+ return mock(AppOpsManager.class, invocationOnMock -> {
+ if (invocationOnMock.getMethod().getName().startsWith("noteOp")) {
+ // Mockito will return 0 for non-stubbed method which corresponds to MODE_ALLOWED
+ // and is not what we want.
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return Answers.RETURNS_DEFAULTS.answer(invocationOnMock);
+ });
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testMediaProjectionNotRestricted() throws Exception {
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+
+ assertThat(mStopController.isStartForbidden(
+ createMediaProjection(PACKAGE_NAME))).isFalse();
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testMediaProjectionRestricted() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+
+ assertThat(mStopController.isStartForbidden(mediaProjection)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingNullProjection() throws Exception {
+ assertThat(mStopController.isExemptFromStopping(null,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingInvalidProjection() throws Exception {
+ assertThat(mStopController.isExemptFromStopping(createMediaProjection(null),
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingDisableScreenshareProtections() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ int value = Settings.Global.getInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0);
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
+
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ } finally {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
+ }
+ }
+
+ @Test
+ public void testExemptFromStoppingHasOpProjectMedia() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+ .noteOpNoThrow(eq(AppOpsManager.OP_PROJECT_MEDIA),
+ eq(mediaProjection.uid), eq(mediaProjection.packageName),
+ nullable(String.class),
+ nullable(String.class));
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingHasAppStreamingRole() throws Exception {
+ runWithRole(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ () -> {
+ try {
+ MediaProjectionManagerService.MediaProjection mediaProjection =
+ createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(
+ mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Test
+ public void testExemptFromStoppingIsBugreportAllowlisted() throws Exception {
+ ArraySet<String> packages = SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ if (packages.isEmpty()) {
+ return;
+ }
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(
+ packages.valueAt(0));
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingHasNoDisplay() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(
+ PACKAGE_NAME);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingHasRecordSensitiveContentPermission() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingIsFalse() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testKeyguardLockedStateChanged_unlocked() {
+ mStopController.onKeyguardLockedStateChanged(false);
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testKeyguardLockedStateChanged_locked() {
+ mStopController.onKeyguardLockedStateChanged(true);
+
+ verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_KEYGUARD);
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_callStarts() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_remainsInCall() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_remainsNoCall() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_callEnds() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_CALL_END);
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testExemptFromStopping_callEnd_callBeforeMediaProjection() throws Exception {
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_CALL_END)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testExemptFromStopping_callEnd_callAfterMediaProjection() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_CALL_END)).isTrue();
+ }
+
+ private MediaProjectionManagerService.MediaProjection createMediaProjection()
+ throws NameNotFoundException {
+ return createMediaProjection(PACKAGE_NAME);
+ }
+
+ private MediaProjectionManagerService.MediaProjection createMediaProjection(String packageName)
+ throws NameNotFoundException {
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(Mockito.isNull(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ return mService.createProjectionInternal(UID, packageName,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(),
+ INVALID_DISPLAY);
+ }
+
+ /**
+ * Run the provided block giving the current context's package the provided role.
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void runWithRole(String role, Runnable block) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ String packageName = mContext.getPackageName();
+ UserHandle user = instrumentation.getTargetContext().getUser();
+ RoleManager roleManager = Objects.requireNonNull(
+ mContext.getSystemService(RoleManager.class));
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+ roleManager.setBypassingRoleQualification(true);
+ roleManager.addRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), success -> {
+ if (success) {
+ latch.countDown();
+ } else {
+ Assert.fail("Couldn't set role for test (failure) " + role);
+ }
+ });
+ assertWithMessage("Couldn't set role for test (timeout) : " + role)
+ .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+ block.run();
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ roleManager.removeRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), (aBool) -> {
+ });
+ roleManager.setBypassingRoleQualification(false);
+ instrumentation.getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
index a54501029712..f45eddcf4480 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.when;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.test.mock.MockContentProvider;
@@ -58,6 +59,7 @@ public final class CallLogQueryHelperTest {
private MatrixCursor mCursor;
private EventConsumer mEventConsumer;
private CallLogQueryHelper mHelper;
+ private CallLogContentProvider mCallLogContentProvider;
@Before
public void setUp() {
@@ -66,7 +68,8 @@ public final class CallLogQueryHelperTest {
mCursor = new MatrixCursor(CALL_LOG_COLUMNS);
MockContentResolver contentResolver = new MockContentResolver();
- contentResolver.addProvider(CALL_LOG_AUTHORITY, new CallLogContentProvider());
+ mCallLogContentProvider = new CallLogContentProvider();
+ contentResolver.addProvider(CALL_LOG_AUTHORITY, mCallLogContentProvider);
when(mContext.getContentResolver()).thenReturn(contentResolver);
mEventConsumer = new EventConsumer();
@@ -80,6 +83,12 @@ public final class CallLogQueryHelperTest {
}
@Test
+ public void testQueryWithSQLiteException() {
+ mCallLogContentProvider.setThrowSQLiteException(true);
+ assertFalse(mHelper.querySince(50L));
+ }
+
+ @Test
public void testQueryIncomingCall() {
mCursor.addRow(new Object[] {
NORMALIZED_PHONE_NUMBER, /* date= */ 100L, /* duration= */ 30L,
@@ -159,11 +168,20 @@ public final class CallLogQueryHelperTest {
}
private class CallLogContentProvider extends MockContentProvider {
+ private boolean mThrowSQLiteException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (mThrowSQLiteException) {
+ throw new SQLiteException();
+ }
+
return mCursor;
}
+
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 16a02b678511..1daee39ce9de 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -99,6 +99,14 @@ public final class ContactsQueryHelperTest {
}
@Test
+ public void testQueryOtherException_returnsFalse() {
+ contentProvider.setThrowOtherException(true);
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+ assertFalse(mHelper.query(contactUri.toString()));
+ }
+
+ @Test
public void testQueryIllegalArgumentException_returnsFalse() {
contentProvider.setThrowIllegalArgumentException(true);
@@ -152,6 +160,13 @@ public final class ContactsQueryHelperTest {
}
@Test
+ public void testQueryWithPhoneNumber_otherExceptionReturnsFalse() {
+ contentProvider.setThrowOtherException(true);
+ String contactUri = "tel:" + PHONE_NUMBER;
+ assertFalse(mHelper.query(contactUri));
+ }
+
+ @Test
public void testQueryWithEmail() {
mContactsLookupCursor.addRow(new Object[] {
/* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 0 });
@@ -188,6 +203,7 @@ public final class ContactsQueryHelperTest {
private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
private boolean mThrowSQLiteException = false;
private boolean mThrowIllegalArgumentException = false;
+ private boolean mThrowOtherException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
@@ -198,6 +214,9 @@ public final class ContactsQueryHelperTest {
if (mThrowIllegalArgumentException) {
throw new IllegalArgumentException();
}
+ if (mThrowOtherException) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
if (uri.isPathPrefixMatch(prefixUri)) {
@@ -215,6 +234,10 @@ public final class ContactsQueryHelperTest {
this.mThrowIllegalArgumentException = throwException;
}
+ public void setThrowOtherException(boolean throwException) {
+ this.mThrowOtherException = throwException;
+ }
+
private void registerCursor(Uri uriPrefix, Cursor cursor) {
mUriPrefixToCursorMap.put(uriPrefix, cursor);
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java
index 7730890e1486..9f4a43df6de8 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.Telephony.BaseMmsColumns;
import android.provider.Telephony.Mms;
@@ -63,6 +64,7 @@ public final class MmsQueryHelperTest {
private final List<MatrixCursor> mAddrCursors = new ArrayList<>();
private EventConsumer mEventConsumer;
private MmsQueryHelper mHelper;
+ private MmsContentProvider mMmsContentProvider;
@Before
public void setUp() {
@@ -73,7 +75,8 @@ public final class MmsQueryHelperTest {
mAddrCursors.add(new MatrixCursor(ADDR_COLUMNS));
MockContentResolver contentResolver = new MockContentResolver();
- contentResolver.addProvider(MMS_AUTHORITY, new MmsContentProvider());
+ mMmsContentProvider = new MmsContentProvider();
+ contentResolver.addProvider(MMS_AUTHORITY, mMmsContentProvider);
when(mContext.getContentResolver()).thenReturn(contentResolver);
mEventConsumer = new EventConsumer();
@@ -87,6 +90,12 @@ public final class MmsQueryHelperTest {
}
@Test
+ public void testQueryWithSQLiteException() {
+ mMmsContentProvider.setThrowSQLiteException(true);
+ assertFalse(mHelper.querySince(50_000L));
+ }
+
+ @Test
public void testQueryIncomingMessage() {
mMmsCursor.addRow(new Object[] {
/* id= */ 0, /* date= */ 100L, /* msgBox= */ BaseMmsColumns.MESSAGE_BOX_INBOX });
@@ -159,10 +168,15 @@ public final class MmsQueryHelperTest {
}
private class MmsContentProvider extends MockContentProvider {
+ private boolean mThrowSQLiteException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (mThrowSQLiteException) {
+ throw new SQLiteException();
+ }
+
List<String> segments = uri.getPathSegments();
if (segments.size() == 2 && "addr".equals(segments.get(1))) {
int messageId = Integer.valueOf(segments.get(0));
@@ -170,5 +184,9 @@ public final class MmsQueryHelperTest {
}
return mMmsCursor;
}
+
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java
index 5cb8cb4fe9f1..09a0dff77eb0 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.Telephony.Sms;
import android.provider.Telephony.TextBasedSmsColumns;
@@ -59,6 +60,7 @@ public final class SmsQueryHelperTest {
private MatrixCursor mSmsCursor;
private EventConsumer mEventConsumer;
private SmsQueryHelper mHelper;
+ private SmsContentProvider mSmsContentProvider;
@Before
public void setUp() {
@@ -67,7 +69,8 @@ public final class SmsQueryHelperTest {
mSmsCursor = new MatrixCursor(SMS_COLUMNS);
MockContentResolver contentResolver = new MockContentResolver();
- contentResolver.addProvider(SMS_AUTHORITY, new SmsContentProvider());
+ mSmsContentProvider = new SmsContentProvider();
+ contentResolver.addProvider(SMS_AUTHORITY, mSmsContentProvider);
when(mContext.getContentResolver()).thenReturn(contentResolver);
mEventConsumer = new EventConsumer();
@@ -130,6 +133,12 @@ public final class SmsQueryHelperTest {
assertEquals(110L, events.get(1).getTimestamp());
}
+ @Test
+ public void testQueryWithSQLiteException() {
+ mSmsContentProvider.setThrowSQLiteException(true);
+ assertFalse(mHelper.querySince(50L));
+ }
+
private class EventConsumer implements BiConsumer<String, Event> {
private final Map<String, List<Event>> mEventMap = new ArrayMap<>();
@@ -141,11 +150,19 @@ public final class SmsQueryHelperTest {
}
private class SmsContentProvider extends MockContentProvider {
+ private boolean mThrowSQLiteException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (mThrowSQLiteException) {
+ throw new SQLiteException();
+ }
return mSmsCursor;
}
+
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index fac5c1f94e56..71a05f3b8509 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -40,11 +40,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.Range;
+import com.android.internal.R;
import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
@@ -87,6 +89,7 @@ public final class ShareTargetPredictorTest {
private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
@Mock private Context mContext;
+ @Mock private Resources mResources;
@Mock private DataManager mDataManager;
@Mock private Consumer<List<AppTarget>> mUpdatePredictionsMethod;
@Mock private PackageData mPackageData1;
@@ -116,11 +119,14 @@ public final class ShareTargetPredictorTest {
when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
- when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+ when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
Context.APP_PREDICTION_SERVICE);
when(mContext.getSystemService(AppPredictionManager.class))
.thenReturn(new AppPredictionManager(mContext));
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getString(R.string.config_chooserActivity))
+ .thenReturn("com.android.intentresolver/.ChooserActivity");
Bundle bundle = new Bundle();
bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
@@ -280,6 +286,25 @@ public final class ShareTargetPredictorTest {
}
@Test
+ public void testPredictTargets_emptyIntentFilter() {
+ Bundle bundle = new Bundle();
+ IntentFilter filter = new IntentFilter();
+ bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, filter);
+ AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
+ .setUiSurface(UI_SURFACE_SHARE)
+ .setPredictedTargetCount(NUM_PREDICTED_TARGETS)
+ .setExtras(bundle)
+ .build();
+ mPredictor = new ShareTargetPredictor(
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+
+ mPredictor.predictTargets();
+
+ verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+ assertThat(mAppTargetCaptor.getValue()).isEmpty();
+ }
+
+ @Test
public void testPredictTargets_noSharingHistoryRankedByShortcutRank() {
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3));
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2));
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 4a43c2e6c180..9d7b6a171bd4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -977,11 +977,19 @@ public final class BackgroundInstallControlServiceTest {
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+ // Test that notifyAllCallbacks doesn't trigger for non-background-installed package
+ mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid);
mTestLooper.dispatchAll();
assertEquals(1, packages.size());
assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+ verify(mCallbackHelper)
+ .notifyAllCallbacks(
+ USER_ID_1,
+ PACKAGE_NAME_1,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 3e748ffb37e9..2c1e37beda26 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -52,6 +52,7 @@ import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
@@ -273,6 +274,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
public String getPackageName() {
return SYSTEM_PACKAGE_NAME;
}
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
}
/** ShortcutService with injection override methods. */
@@ -665,6 +671,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
protected ServiceContext mServiceContext;
protected ClientContext mClientContext;
+ protected ContentResolver mContentResolver;
protected ShortcutServiceTestable mService;
protected ShortcutManagerTestable mManager;
@@ -861,6 +868,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
mServiceContext = spy(new ServiceContext());
mClientContext = new ClientContext();
+ mContentResolver = mock(ContentResolver.class);
mMockPackageManager = mock(PackageManager.class);
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
@@ -982,6 +990,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
return userProperties;
});
+ when(mMockUserManagerInternal.getUserInfos()).thenReturn(
+ mUserInfos.values().toArray(new UserInfo[0]));
// User 0 and P0 are always running
mRunningUsers.put(USER_0, true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java
new file mode 100644
index 000000000000..d69e47684f8b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.app.LocaleManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.multiuser.Flags;
+import android.os.LocaleList;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Postsubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Test {@link UserManager Cache} functionality.
+ *
+ * atest com.android.server.pm.UserManagerCacheTest
+ */
+@Postsubmit
+@RunWith(AndroidJUnit4.class)
+public final class UserManagerCacheTest {
+
+ private static final LocaleList TEST_LOCALE_LIST = LocaleList.forLanguageTags("pl-PL");
+ private static final long SLEEP_TIMEOUT = 5_000;
+ private static final int REMOVE_USER_TIMEOUT_SECONDS = 180; // 180 seconds
+ private static final String TAG = UserManagerCacheTest.class.getSimpleName();
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private UserManager mUserManager = null;
+ private PackageManager mPackageManager;
+ private LocaleManager mLocaleManager;
+ private ArraySet<Integer> mUsersToRemove;
+ private UserRemovalWaiter mUserRemovalWaiter;
+ private int mOriginalCurrentUserId;
+ private LocaleList mSystemLocales;
+
+ @Before
+ public void setUp() throws Exception {
+ mOriginalCurrentUserId = ActivityManager.getCurrentUser();
+ mUserManager = UserManager.get(mContext);
+ mPackageManager = mContext.getPackageManager();
+ mLocaleManager = mContext.getSystemService(LocaleManager.class);
+ mSystemLocales = mLocaleManager.getSystemLocales();
+ mUserRemovalWaiter = new UserRemovalWaiter(mContext, TAG, REMOVE_USER_TIMEOUT_SECONDS);
+ mUsersToRemove = new ArraySet<>();
+ removeExistingUsers();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Making a copy of mUsersToRemove to avoid ConcurrentModificationException
+ mUsersToRemove.stream().toList().forEach(this::removeUser);
+ mUserRemovalWaiter.close();
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ mContext.getUser());
+ mLocaleManager.setSystemLocales(mSystemLocales);
+ }
+
+ private void removeExistingUsers() {
+ int currentUser = ActivityManager.getCurrentUser();
+
+ UserHandle communalProfile = mUserManager.getCommunalProfile();
+ int communalProfileId = communalProfile != null
+ ? communalProfile.getIdentifier() : UserHandle.USER_NULL;
+
+ List<UserInfo> list = mUserManager.getUsers();
+ for (UserInfo user : list) {
+ // Keep system and current user
+ if (user.id != UserHandle.USER_SYSTEM
+ && user.id != currentUser
+ && user.id != communalProfileId
+ && !user.isMain()) {
+ removeUser(user.id);
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CACHE_USER_INFO_READ_ONLY)
+ public void testUserInfoAfterLocaleChange() throws Exception {
+ UserInfo userInfo = mUserManager.createGuest(mContext);
+ mUsersToRemove.add(userInfo.id);
+ assertThat(userInfo).isNotNull();
+
+ UserInfo guestUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(guestUserInfo).isNotNull();
+ assertThat(guestUserInfo.name).isNotEqualTo("Gość");
+
+ UserInfo ownerUserInfo = mUserManager.getUserInfo(mOriginalCurrentUserId);
+ assertThat(ownerUserInfo).isNotNull();
+ assertThat(ownerUserInfo.name).isNotEqualTo("Właściciel");
+
+ mLocaleManager.setSystemLocales(TEST_LOCALE_LIST);
+ SystemClock.sleep(SLEEP_TIMEOUT);
+ UserInfo guestUserInfoPl = mUserManager.getUserInfo(userInfo.id);
+ UserInfo ownerUserInfoPl = mUserManager.getUserInfo(mOriginalCurrentUserId);
+
+ assertThat(guestUserInfoPl).isNotNull();
+ assertThat(guestUserInfoPl.name).isEqualTo("Gość");
+
+ assertThat(ownerUserInfoPl).isNotNull();
+ assertThat(ownerUserInfoPl.name).isEqualTo("Właściciel");
+ }
+
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CACHE_USER_INFO_READ_ONLY)
+ public void testGetUserInfo10kSpam() throws Exception {
+ UserInfo cachedUserInfo = mUserManager.getUserInfo(mOriginalCurrentUserId);
+ for (int i = 0; i < 10000; i++) {
+ // Control how often cache is calling the API
+ UserInfo ownerUserInfo = mUserManager.getUserInfo(mOriginalCurrentUserId);
+ assertThat(ownerUserInfo).isNotNull();
+ // If indeed it was chached then objects should stay the same. We use == to compare
+ // object addresses to make sure UserInfo is not new copy of the same UserInfo.
+ assertThat(cachedUserInfo.toFullString()).isEqualTo(ownerUserInfo.toFullString());
+ }
+ }
+
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CACHE_USER_INFO_READ_ONLY)
+ public void testSetUserAdmin() throws Exception {
+ UserInfo userInfo = mUserManager.createUser("SecondaryUser",
+ UserManager.USER_TYPE_FULL_SECONDARY, /*flags=*/ 0);
+ mUsersToRemove.add(userInfo.id);
+ // cache user
+ UserInfo cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+
+ assertThat(userInfo.isAdmin()).isFalse();
+ assertThat(cachedUserInfo.isAdmin()).isFalse();
+
+ // invalidate cache
+ mUserManager.setUserAdmin(userInfo.id);
+
+ // updated UserInfo should be returned
+ cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(cachedUserInfo.isAdmin()).isTrue();
+ }
+
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CACHE_USER_INFO_READ_ONLY)
+ public void testRevokeUserAdmin() throws Exception {
+ UserInfo userInfo = mUserManager.createUser("Admin",
+ UserManager.USER_TYPE_FULL_SECONDARY, /*flags=*/ UserInfo.FLAG_ADMIN);
+ mUsersToRemove.add(userInfo.id);
+ // cache user
+ UserInfo cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(userInfo.isAdmin()).isTrue();
+ assertThat(cachedUserInfo.isAdmin()).isTrue();
+
+ // invalidate cache
+ mUserManager.revokeUserAdmin(userInfo.id);
+
+ // updated UserInfo should be returned
+ cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(cachedUserInfo.isAdmin()).isFalse();
+ }
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CACHE_USER_INFO_READ_ONLY)
+ public void testRevokeUserAdminFromNonAdmin() throws Exception {
+ UserInfo userInfo = mUserManager.createUser("NonAdmin",
+ UserManager.USER_TYPE_FULL_SECONDARY, /*flags=*/ 0);
+ mUsersToRemove.add(userInfo.id);
+ // cache user
+ UserInfo cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(userInfo.isAdmin()).isFalse();
+ assertThat(cachedUserInfo.isAdmin()).isFalse();
+
+ // invalidate cache
+ mUserManager.revokeUserAdmin(userInfo.id);
+
+ // updated UserInfo should be returned
+ cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(cachedUserInfo.isAdmin()).isFalse();
+ }
+
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_CACHE_USER_INFO_READ_ONLY)
+ public void testSetUserName_withContextUserId() throws Exception {
+ assumeManagedUsersSupported();
+ final String newName = "Managed_user 1";
+ final int mainUserId = mUserManager.getMainUser().getIdentifier();
+
+ // cache main user
+ UserInfo mainUserInfo = mUserManager.getUserInfo(mainUserId);
+
+ assertThat(mainUserInfo).isNotNull();
+
+ // invalidate cache
+ UserInfo userInfo = mUserManager.createProfileForUser("Managed 1",
+ UserManager.USER_TYPE_PROFILE_MANAGED, 0, mainUserId, null);
+ mUsersToRemove.add(userInfo.id);
+ // cache user
+ UserInfo cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ // updated cache for main user
+ mainUserInfo = mUserManager.getUserInfo(mainUserId);
+
+ assertThat(userInfo).isNotNull();
+ assertThat(cachedUserInfo).isNotNull();
+ assertThat(mainUserInfo).isNotNull();
+ // profileGroupId are the same after adding profile to user.
+ assertThat(mainUserInfo.profileGroupId).isEqualTo(cachedUserInfo.profileGroupId);
+
+ UserManager um = (UserManager) mContext.createPackageContextAsUser(
+ "android", 0, userInfo.getUserHandle())
+ .getSystemService(Context.USER_SERVICE);
+ // invalidate cache
+ um.setUserName(newName);
+
+ // updated UserInfo should be returned
+ cachedUserInfo = mUserManager.getUserInfo(userInfo.id);
+ assertThat(cachedUserInfo.name).isEqualTo(newName);
+
+ // get user name from getUserName using context.getUserId
+ assertThat(um.getUserName()).isEqualTo(newName);
+ }
+
+ private void assumeManagedUsersSupported() {
+ // In Automotive, if headless system user is enabled, a managed user cannot be created
+ // under a primary user.
+ assumeTrue("device doesn't support managed users",
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)
+ && (!isAutomotive() || !UserManager.isHeadlessSystemUserMode()));
+ }
+
+ private void removeUser(int userId) {
+ mUserManager.removeUser(userId);
+ mUserRemovalWaiter.waitFor(userId);
+ mUsersToRemove.remove(userId);
+ }
+
+ private boolean isAutomotive() {
+ return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 1331ae173b18..b110ff6b6bc4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -160,6 +160,7 @@ public class UserManagerServiceUserPropertiesTest {
// Make a possibly-not-full-permission (i.e. partial) copy and check that it is correct.
final UserProperties copy = new UserProperties(orig, exposeAll, hasManage, hasQuery);
+ assertThat(copy.toString()).isNotNull();
verifyTestCopyLacksPermissions(orig, copy, exposeAll, hasManage, hasQuery);
if (permLevel < 1) {
// PropertiesPresent should definitely be different since not all items were copied.
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index e652df5cdf1a..f9946604ad5d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -539,6 +539,79 @@ public final class UserManagerTest {
@MediumTest
@Test
+ public void testRemoveUser_shouldRemovePrivateUser() {
+ UserInfo privateProfileUser =
+ createProfileForUser(
+ "Private profile",
+ UserManager.USER_TYPE_PROFILE_PRIVATE,
+ mUserManager.getMainUser().getIdentifier());
+ assertThat(privateProfileUser).isNotNull();
+ assertThat(hasUser(privateProfileUser.id)).isTrue();
+
+ removeUser(privateProfileUser.id);
+
+ assertThat(hasUser(privateProfileUser.id)).isFalse();
+ }
+
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(
+ android.multiuser.Flags.FLAG_IGNORE_RESTRICTIONS_WHEN_DELETING_PRIVATE_PROFILE)
+ public void testRemoveUser_shouldRemovePrivateUser_withDisallowRemoveUserRestriction() {
+ UserHandle mainUser = mUserManager.getMainUser();
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_REMOVE_USER, /* value= */ true, mainUser);
+ try {
+ UserInfo privateProfileUser =
+ createProfileForUser(
+ "Private profile",
+ UserManager.USER_TYPE_PROFILE_PRIVATE,
+ mainUser.getIdentifier());
+ assertThat(privateProfileUser).isNotNull();
+ assertThat(hasUser(privateProfileUser.id)).isTrue();
+ removeUser(privateProfileUser.id);
+
+ assertThat(hasUser(privateProfileUser.id)).isFalse();
+ } finally {
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_REMOVE_USER, /* value= */ false, mainUser);
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testRemoveUser_withDisallowRemoveUserRestrictionAndMultipleUsersPresent() {
+ UserInfo privateProfileUser =
+ createProfileForUser(
+ "Private profile",
+ UserManager.USER_TYPE_PROFILE_PRIVATE,
+ mUserManager.getMainUser().getIdentifier());
+ assertThat(privateProfileUser).isNotNull();
+ assertThat(hasUser(privateProfileUser.id)).isTrue();
+ UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+ assertThat(testUser).isNotNull();
+ assertThat(hasUser(testUser.id)).isTrue();
+ UserHandle mainUser = mUserManager.getMainUser();
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_REMOVE_USER, /* value= */ true, mainUser);
+ try {
+ assertThat(
+ mUserManager.removeUserWhenPossible(
+ testUser.getUserHandle(), /* overrideDevicePolicy= */ false))
+ .isEqualTo(UserManager.REMOVE_RESULT_ERROR_USER_RESTRICTION);
+
+ // Non private profile users should be prevented from being removed.
+ assertThat(mUserManager.removeUser(testUser.id)).isEqualTo(false);
+
+ assertThat(hasUser(testUser.id)).isTrue();
+ } finally {
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_REMOVE_USER, /* value= */ false, mainUser);
+ }
+ }
+
+ @MediumTest
+ @Test
public void testRemoveUserShouldNotRemoveTargetUser_DuringUserSwitch() {
final int startUser = ActivityManager.getCurrentUser();
final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 6d79ae467bf0..2ed71cecd79d 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -22,10 +22,12 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -39,14 +41,18 @@ import android.content.pm.PackageManager;
import android.hardware.thermal.TemperatureThreshold;
import android.hardware.thermal.ThrottlingSeverity;
import android.os.CoolingDevice;
+import android.os.Flags;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.IThermalEventListener;
+import android.os.IThermalHeadroomListener;
import android.os.IThermalService;
import android.os.IThermalStatusListener;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Temperature;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -56,6 +62,8 @@ import com.android.server.power.ThermalManagerService.TemperatureWatcher;
import com.android.server.power.ThermalManagerService.ThermalHalWrapper;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -78,6 +86,11 @@ import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ThermalManagerServiceTest {
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+
private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
private ThermalManagerService mService;
private ThermalHalFake mFakeHal;
@@ -89,6 +102,8 @@ public class ThermalManagerServiceTest {
@Mock
private IThermalService mIThermalServiceMock;
@Mock
+ private IThermalHeadroomListener mHeadroomListener;
+ @Mock
private IThermalEventListener mEventListener1;
@Mock
private IThermalEventListener mEventListener2;
@@ -102,22 +117,23 @@ public class ThermalManagerServiceTest {
*/
private class ThermalHalFake extends ThermalHalWrapper {
private static final int INIT_STATUS = Temperature.THROTTLING_NONE;
- private ArrayList<Temperature> mTemperatureList = new ArrayList<>();
- private ArrayList<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
- private ArrayList<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();
+ private List<Temperature> mTemperatureList = new ArrayList<>();
+ private List<Temperature> mOverrideTemperatures = null;
+ private List<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
+ private List<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();
- private Temperature mSkin1 = new Temperature(0, Temperature.TYPE_SKIN, "skin1",
+ private Temperature mSkin1 = new Temperature(28, Temperature.TYPE_SKIN, "skin1",
INIT_STATUS);
- private Temperature mSkin2 = new Temperature(0, Temperature.TYPE_SKIN, "skin2",
+ private Temperature mSkin2 = new Temperature(31, Temperature.TYPE_SKIN, "skin2",
INIT_STATUS);
- private Temperature mBattery = new Temperature(0, Temperature.TYPE_BATTERY, "batt",
+ private Temperature mBattery = new Temperature(34, Temperature.TYPE_BATTERY, "batt",
INIT_STATUS);
- private Temperature mUsbPort = new Temperature(0, Temperature.TYPE_USB_PORT, "usbport",
+ private Temperature mUsbPort = new Temperature(37, Temperature.TYPE_USB_PORT, "usbport",
INIT_STATUS);
- private CoolingDevice mCpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "cpu");
- private CoolingDevice mGpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "gpu");
+ private CoolingDevice mCpu = new CoolingDevice(40, CoolingDevice.TYPE_BATTERY, "cpu");
+ private CoolingDevice mGpu = new CoolingDevice(43, CoolingDevice.TYPE_BATTERY, "gpu");
- private ArrayList<TemperatureThreshold> initializeThresholds() {
+ private List<TemperatureThreshold> initializeThresholds() {
ArrayList<TemperatureThreshold> thresholds = new ArrayList<>();
TemperatureThreshold skinThreshold = new TemperatureThreshold();
@@ -157,6 +173,14 @@ public class ThermalManagerServiceTest {
mCoolingDeviceList.add(mGpu);
}
+ void setOverrideTemperatures(List<Temperature> temperatures) {
+ mOverrideTemperatures = temperatures;
+ }
+
+ void resetOverrideTemperatures() {
+ mOverrideTemperatures = null;
+ }
+
@Override
protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) {
List<Temperature> ret = new ArrayList<>();
@@ -221,22 +245,36 @@ public class ThermalManagerServiceTest {
when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
resetListenerMock();
mService = new ThermalManagerService(mContext, mFakeHal);
- // Register callbacks before AMS ready and no callback sent
+ mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ }
+
+ private void resetListenerMock() {
+ reset(mEventListener1);
+ reset(mStatusListener1);
+ reset(mEventListener2);
+ reset(mStatusListener2);
+ reset(mHeadroomListener);
+ doReturn(mock(IBinder.class)).when(mEventListener1).asBinder();
+ doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder();
+ doReturn(mock(IBinder.class)).when(mEventListener2).asBinder();
+ doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder();
+ doReturn(mock(IBinder.class)).when(mHeadroomListener).asBinder();
+ }
+
+ @Test
+ public void testRegister() throws Exception {
+ mService = new ThermalManagerService(mContext, mFakeHal);
+ // Register callbacks before AMS ready and verify they are called after AMS is ready
assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
Temperature.TYPE_SKIN));
assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
- verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
- .times(0)).notifyThrottling(any(Temperature.class));
- verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
- .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
- verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
- .times(0)).notifyThrottling(any(Temperature.class));
- verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
- .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
resetListenerMock();
mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ assertTrue(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+
ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(4)).notifyThrottling(captor.capture());
@@ -251,31 +289,18 @@ public class ThermalManagerServiceTest {
captor.getAllValues());
verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(0)).onStatusChange(Temperature.THROTTLING_NONE);
- }
-
- private void resetListenerMock() {
- reset(mEventListener1);
- reset(mStatusListener1);
- reset(mEventListener2);
- reset(mStatusListener2);
- doReturn(mock(IBinder.class)).when(mEventListener1).asBinder();
- doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder();
- doReturn(mock(IBinder.class)).when(mEventListener2).asBinder();
- doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder();
- }
-
- @Test
- public void testRegister() throws RemoteException {
resetListenerMock();
- // Register callbacks and verify they are called
+
+ // Register callbacks after AMS ready and verify they are called
assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
- ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
+ captor = ArgumentCaptor.forClass(Temperature.class);
verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(4)).notifyThrottling(captor.capture());
assertListEqualsIgnoringOrder(mFakeHal.mTemperatureList, captor.getAllValues());
verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+
// Register new callbacks and verify old ones are not called (remained same) while new
// ones are called
assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
@@ -296,11 +321,19 @@ public class ThermalManagerServiceTest {
}
@Test
- public void testNotify() throws RemoteException {
+ public void testNotifyThrottling() throws Exception {
+ assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+ assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+ Temperature.TYPE_SKIN));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
+ Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
+ resetListenerMock();
+
int status = Temperature.THROTTLING_SEVERE;
// Should only notify event not status
Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
- mFakeHal.mCallback.onValues(newBattery);
+ mFakeHal.mCallback.onTemperatureChanged(newBattery);
verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).notifyThrottling(newBattery);
verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
@@ -312,7 +345,7 @@ public class ThermalManagerServiceTest {
resetListenerMock();
// Notify both event and status
Temperature newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
- mFakeHal.mCallback.onValues(newSkin);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).notifyThrottling(newSkin);
verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
@@ -325,7 +358,7 @@ public class ThermalManagerServiceTest {
// Back to None, should only notify event not status
status = Temperature.THROTTLING_NONE;
newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
- mFakeHal.mCallback.onValues(newBattery);
+ mFakeHal.mCallback.onTemperatureChanged(newBattery);
verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).notifyThrottling(newBattery);
verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
@@ -337,7 +370,7 @@ public class ThermalManagerServiceTest {
resetListenerMock();
// Should also notify status
newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
- mFakeHal.mCallback.onValues(newSkin);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).notifyThrottling(newSkin);
verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
@@ -349,6 +382,57 @@ public class ThermalManagerServiceTest {
}
@Test
+ @EnableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK})
+ public void testNotifyThrottling_headroomCallback() throws Exception {
+ assertTrue(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+ Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
+ resetListenerMock();
+ int status = Temperature.THROTTLING_SEVERE;
+ mFakeHal.setOverrideTemperatures(new ArrayList<>());
+
+ // Should not notify on non-skin type
+ Temperature newBattery = new Temperature(37, Temperature.TYPE_BATTERY, "batt", status);
+ mFakeHal.mCallback.onTemperatureChanged(newBattery);
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
+ resetListenerMock();
+
+ // Notify headroom on skin temperature change
+ Temperature newSkin = new Temperature(37, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onHeadroomChange(eq(0.9f), anyFloat(), anyInt(),
+ eq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+ 1.5f}));
+ resetListenerMock();
+
+ // Same or similar temperature should not trigger in a short period
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
+ newSkin = new Temperature(36.9f, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
+ newSkin = new Temperature(37.1f, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
+ resetListenerMock();
+
+ // Significant temperature should trigger in a short period
+ newSkin = new Temperature(34f, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onHeadroomChange(eq(0.8f), anyFloat(), anyInt(),
+ eq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+ 1.5f}));
+ resetListenerMock();
+ newSkin = new Temperature(40f, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onHeadroomChange(eq(1.0f), anyFloat(), anyInt(),
+ eq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+ 1.5f}));
+ }
+
+ @Test
public void testGetCurrentTemperatures() throws RemoteException {
assertListEqualsIgnoringOrder(mFakeHal.getCurrentTemperatures(false, 0),
Arrays.asList(mService.mService.getCurrentTemperatures()));
@@ -362,7 +446,7 @@ public class ThermalManagerServiceTest {
public void testGetCurrentStatus() throws RemoteException {
int status = Temperature.THROTTLING_SEVERE;
Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
- mFakeHal.mCallback.onValues(newSkin);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
assertEquals(status, mService.mService.getCurrentThermalStatus());
int battStatus = Temperature.THROTTLING_EMERGENCY;
Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", battStatus);
@@ -373,11 +457,11 @@ public class ThermalManagerServiceTest {
public void testThermalShutdown() throws RemoteException {
int status = Temperature.THROTTLING_SHUTDOWN;
Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
- mFakeHal.mCallback.onValues(newSkin);
+ mFakeHal.mCallback.onTemperatureChanged(newSkin);
verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", status);
- mFakeHal.mCallback.onValues(newBattery);
+ mFakeHal.mCallback.onTemperatureChanged(newBattery);
verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false);
}
@@ -388,13 +472,28 @@ public class ThermalManagerServiceTest {
// Do no call onActivityManagerReady to skip connect HAL
assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
- assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
- assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
+ assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+ Temperature.TYPE_SKIN));
+ assertFalse(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).notifyThrottling(any(Temperature.class));
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).notifyThrottling(any(Temperature.class));
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
+
assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperatures()).size());
assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
- Temperature.TYPE_SKIN)).size());
+ Temperature.TYPE_SKIN)).size());
assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(0)));
+
+ assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.unregisterThermalEventListener(mEventListener2));
+ assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
+ assertFalse(mService.mService.unregisterThermalHeadroomListener(mHeadroomListener));
}
@Test
@@ -419,15 +518,45 @@ public class ThermalManagerServiceTest {
}
@Test
- public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException {
+ @EnableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK,
+ Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS})
+ public void testTemperatureWatcherUpdateSevereThresholds() throws Exception {
+ assertTrue(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onHeadroomChange(eq(0.6f), eq(0.6f), anyInt(),
+ aryEq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+ 1.5f}));
+ resetListenerMock();
TemperatureWatcher watcher = mService.mTemperatureWatcher;
- watcher.mSevereThresholds.erase();
- watcher.updateThresholds();
- assertEquals(1, watcher.mSevereThresholds.size());
- assertEquals("skin1", watcher.mSevereThresholds.keyAt(0));
- Float threshold = watcher.mSevereThresholds.get("skin1");
- assertNotNull(threshold);
- assertEquals(40.0f, threshold, 0.0f);
+ TemperatureThreshold newThreshold = new TemperatureThreshold();
+ newThreshold.name = "skin1";
+ newThreshold.type = Temperature.TYPE_SKIN;
+ // significant change in threshold (> 0.3C) should trigger a callback
+ newThreshold.hotThrottlingThresholds = new float[]{
+ Float.NaN, 43.0f, 46.0f, 49.0f, Float.NaN, Float.NaN, Float.NaN
+ };
+ mFakeHal.mCallback.onThresholdChanged(newThreshold);
+ synchronized (watcher.mSamples) {
+ Float threshold = watcher.mSevereThresholds.get("skin1");
+ assertNotNull(threshold);
+ assertEquals(49.0f, threshold, 0.0f);
+ assertArrayEquals("Got" + Arrays.toString(watcher.mHeadroomThresholds),
+ new float[]{Float.NaN, 0.8f, 0.9f, 1.0f, Float.NaN, Float.NaN, Float.NaN},
+ watcher.mHeadroomThresholds, 0.01f);
+ }
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onHeadroomChange(eq(0.3f), eq(0.3f), anyInt(),
+ aryEq(new float[]{Float.NaN, 0.8f, 0.9f, 1.0f, Float.NaN, Float.NaN, Float.NaN}));
+ resetListenerMock();
+
+ // same or similar threshold callback data within a second should not trigger callback
+ mFakeHal.mCallback.onThresholdChanged(newThreshold);
+ newThreshold.hotThrottlingThresholds = new float[]{
+ Float.NaN, 43.1f, 45.9f, 49.0f, Float.NaN, Float.NaN, Float.NaN
+ };
+ mFakeHal.mCallback.onThresholdChanged(newThreshold);
+ verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
}
@Test
@@ -436,57 +565,57 @@ public class ThermalManagerServiceTest {
synchronized (watcher.mSamples) {
Arrays.fill(watcher.mHeadroomThresholds, Float.NaN);
}
- watcher.updateHeadroomThreshold(ThrottlingSeverity.LIGHT, 40, 49);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.MODERATE, 46, 49);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.SEVERE, 49, 49);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.CRITICAL, 64, 49);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.EMERGENCY, 70, 49);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.SHUTDOWN, 79, 49);
+ TemperatureThreshold threshold = new TemperatureThreshold();
+ threshold.hotThrottlingThresholds = new float[]{Float.NaN, 40, 46, 49, 64, 70, 79};
synchronized (watcher.mSamples) {
+ watcher.updateTemperatureThresholdLocked(threshold, false /*override*/);
assertArrayEquals(new float[]{Float.NaN, 0.7f, 0.9f, 1.0f, 1.5f, 1.7f, 2.0f},
watcher.mHeadroomThresholds, 0.01f);
}
// when another sensor reports different threshold, we expect to see smaller one to be used
- watcher.updateHeadroomThreshold(ThrottlingSeverity.LIGHT, 37, 52);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.MODERATE, 46, 52);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.SEVERE, 52, 52);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.CRITICAL, 64, 52);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.EMERGENCY, 100, 52);
- watcher.updateHeadroomThreshold(ThrottlingSeverity.SHUTDOWN, 200, 52);
+ threshold = new TemperatureThreshold();
+ threshold.hotThrottlingThresholds = new float[]{Float.NaN, 37, 46, 52, 64, 100, 200};
synchronized (watcher.mSamples) {
+ watcher.updateTemperatureThresholdLocked(threshold, false /*override*/);
assertArrayEquals(new float[]{Float.NaN, 0.5f, 0.8f, 1.0f, 1.4f, 1.7f, 2.0f},
watcher.mHeadroomThresholds, 0.01f);
}
}
@Test
- public void testGetThermalHeadroomThresholdsOnlyReadOnce() throws Exception {
+ public void testGetThermalHeadroomThresholds() throws Exception {
float[] expected = new float[]{Float.NaN, 0.1f, 0.2f, 0.3f, 0.4f, Float.NaN, 0.6f};
when(mIThermalServiceMock.getThermalHeadroomThresholds()).thenReturn(expected);
Map<Integer, Float> thresholds1 = mPowerManager.getThermalHeadroomThresholds();
verify(mIThermalServiceMock, times(1)).getThermalHeadroomThresholds();
+ checkHeadroomThresholds(expected, thresholds1);
+
+ reset(mIThermalServiceMock);
+ expected = new float[]{Float.NaN, 0.2f, 0.3f, 0.4f, 0.4f, Float.NaN, 0.6f};
+ when(mIThermalServiceMock.getThermalHeadroomThresholds()).thenReturn(expected);
+ Map<Integer, Float> thresholds2 = mPowerManager.getThermalHeadroomThresholds();
+ verify(mIThermalServiceMock, times(1)).getThermalHeadroomThresholds();
+ checkHeadroomThresholds(expected, thresholds2);
+ }
+
+ private void checkHeadroomThresholds(float[] expected, Map<Integer, Float> thresholds) {
for (int status = PowerManager.THERMAL_STATUS_LIGHT;
status <= PowerManager.THERMAL_STATUS_SHUTDOWN; status++) {
if (Float.isNaN(expected[status])) {
- assertFalse(thresholds1.containsKey(status));
+ assertFalse(thresholds.containsKey(status));
} else {
- assertEquals(expected[status], thresholds1.get(status), 0.01f);
+ assertEquals(expected[status], thresholds.get(status), 0.01f);
}
}
- reset(mIThermalServiceMock);
- Map<Integer, Float> thresholds2 = mPowerManager.getThermalHeadroomThresholds();
- verify(mIThermalServiceMock, times(0)).getThermalHeadroomThresholds();
- assertNotSame(thresholds1, thresholds2);
- assertEquals(thresholds1, thresholds2);
}
@Test
- public void testGetThermalHeadroomThresholdsOnDefaultHalResult() throws Exception {
+ public void testGetThermalHeadroomThresholdsOnDefaultHalResult() throws Exception {
TemperatureWatcher watcher = mService.mTemperatureWatcher;
ArrayList<TemperatureThreshold> thresholds = new ArrayList<>();
mFakeHal.mTemperatureThresholdList = thresholds;
- watcher.updateThresholds();
+ watcher.getAndUpdateThresholds();
synchronized (watcher.mSamples) {
assertArrayEquals(
new float[]{Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN,
@@ -496,12 +625,12 @@ public class ThermalManagerServiceTest {
TemperatureThreshold nanThresholds = new TemperatureThreshold();
nanThresholds.name = "nan";
nanThresholds.type = Temperature.TYPE_SKIN;
- nanThresholds.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
- nanThresholds.coldThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
+ nanThresholds.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
+ nanThresholds.coldThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
Arrays.fill(nanThresholds.hotThrottlingThresholds, Float.NaN);
Arrays.fill(nanThresholds.coldThrottlingThresholds, Float.NaN);
thresholds.add(nanThresholds);
- watcher.updateThresholds();
+ watcher.getAndUpdateThresholds();
synchronized (watcher.mSamples) {
assertArrayEquals(
new float[]{Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN,
@@ -593,7 +722,13 @@ public class ThermalManagerServiceTest {
}
@Test
- public void testDump() {
+ public void testDump() throws Exception {
+ assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+ assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+ Temperature.TYPE_SKIN));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
+
when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
.thenReturn(PackageManager.PERMISSION_GRANTED);
final StringWriter out = new StringWriter();
@@ -614,22 +749,22 @@ public class ThermalManagerServiceTest {
assertThat(dumpStr).contains("Thermal Status: 0");
assertThat(dumpStr).contains(
"Cached temperatures:\n"
- + "\tTemperature{mValue=0.0, mType=4, mName=usbport, mStatus=0}\n"
- + "\tTemperature{mValue=0.0, mType=2, mName=batt, mStatus=0}\n"
- + "\tTemperature{mValue=0.0, mType=3, mName=skin1, mStatus=0}\n"
- + "\tTemperature{mValue=0.0, mType=3, mName=skin2, mStatus=0}"
+ + "\tTemperature{mValue=37.0, mType=4, mName=usbport, mStatus=0}\n"
+ + "\tTemperature{mValue=34.0, mType=2, mName=batt, mStatus=0}\n"
+ + "\tTemperature{mValue=28.0, mType=3, mName=skin1, mStatus=0}\n"
+ + "\tTemperature{mValue=31.0, mType=3, mName=skin2, mStatus=0}"
);
assertThat(dumpStr).contains("HAL Ready: true\n"
+ "HAL connection:\n"
+ "\tThermalHAL AIDL 1 connected: yes");
assertThat(dumpStr).contains("Current temperatures from HAL:\n"
- + "\tTemperature{mValue=0.0, mType=3, mName=skin1, mStatus=0}\n"
- + "\tTemperature{mValue=0.0, mType=3, mName=skin2, mStatus=0}\n"
- + "\tTemperature{mValue=0.0, mType=2, mName=batt, mStatus=0}\n"
- + "\tTemperature{mValue=0.0, mType=4, mName=usbport, mStatus=0}\n");
+ + "\tTemperature{mValue=28.0, mType=3, mName=skin1, mStatus=0}\n"
+ + "\tTemperature{mValue=31.0, mType=3, mName=skin2, mStatus=0}\n"
+ + "\tTemperature{mValue=34.0, mType=2, mName=batt, mStatus=0}\n"
+ + "\tTemperature{mValue=37.0, mType=4, mName=usbport, mStatus=0}\n");
assertThat(dumpStr).contains("Current cooling devices from HAL:\n"
- + "\tCoolingDevice{mValue=0, mType=1, mName=cpu}\n"
- + "\tCoolingDevice{mValue=0, mType=1, mName=gpu}\n");
+ + "\tCoolingDevice{mValue=40, mType=1, mName=cpu}\n"
+ + "\tCoolingDevice{mValue=43, mType=1, mName=gpu}\n");
assertThat(dumpStr).contains("Temperature static thresholds from HAL:\n"
+ "\tTemperatureThreshold{mType=3, mName=skin1, mHotThrottlingThresholds=[25.0, "
+ "30.0, 35.0, 40.0, 45.0, 50.0, 55.0], mColdThrottlingThresholds=[0.0, 0.0, 0.0,"
diff --git a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
index 6b32be0b2dfd..1af366b32da9 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
@@ -28,6 +28,7 @@ import static android.view.Display.DEFAULT_DISPLAY_GROUP;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_TOUCH;
import static com.android.server.power.WakefulnessSessionObserver.OFF_REASON_POWER_BUTTON;
+import static com.android.server.power.WakefulnessSessionObserver.OFF_REASON_TIMEOUT;
import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_CANCEL_POWER_BUTTON;
import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION;
import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_SUCCESS;
@@ -40,6 +41,7 @@ import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -90,6 +92,7 @@ public class WakefulnessSessionObserverTest {
mWakefulnessSessionFrameworkStatsLogger;
@Mock
private DisplayManagerInternal mDisplayManagerInternal;
+ private MockContentResolver mContentResolver = new MockContentResolver();
private TestHandler mHandler;
@Before
@@ -106,10 +109,9 @@ public class WakefulnessSessionObserverTest {
R.integer.config_screenTimeoutOverride);
when(mContext.getResources()).thenReturn(res);
FakeSettingsProvider.clearSettingsProvider();
- MockContentResolver mockContentResolver = new MockContentResolver();
- mockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContext.getContentResolver()).thenReturn(mockContentResolver);
- Settings.System.putIntForUser(mockContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT);
final DisplayInfo info = new DisplayInfo();
@@ -511,6 +513,115 @@ public class WakefulnessSessionObserverTest {
DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
}
+ @Test
+ public void testScreenOffTimeout_normal_logSessionEventTriggered() {
+ int powerGroupId = 1;
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY;
+ triggerLogSessionEvent(powerGroupId, userActivity);
+ verify(mWakefulnessSessionFrameworkStatsLogger)
+ .logSessionEvent(
+ powerGroupId, // powerGroupId
+ OFF_REASON_TIMEOUT, // interactiveStateOffReason
+ 0, // interactiveStateOnDurationMs
+ userActivity, // userActivity
+ 0, // lastUserActivityEventDurationMs
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS - OVERRIDE_SCREEN_OFF_TIMEOUT_MS
+ ); // reducedInteractiveStateOnDurationMs;
+ }
+
+ @Test
+ public void testScreenOffTimeout_zero_noLogSessionEventTriggered() {
+ // simulate adding an invalid screen_off_timeout value
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ 0, // invalid timeout value
+ UserHandle.USER_CURRENT);
+ mWakefulnessSessionObserver.updateSettingScreenOffTimeout(mContext);
+
+ try {
+ triggerLogSessionEvent();
+ verify(mWakefulnessSessionFrameworkStatsLogger, never())
+ .logSessionEvent(anyInt(), anyInt(), anyLong(), anyInt(), anyLong(), anyInt());
+ } finally {
+ // rollback the original data
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT);
+ mWakefulnessSessionObserver.updateSettingScreenOffTimeout(mContext);
+ }
+ }
+
+ @Test
+ public void testScreenOffTimeout_negative_noLogSessionEventTriggered() {
+ // simulate adding an invalid screen_off_timeout value
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ -1, // invalid timeout value
+ UserHandle.USER_CURRENT);
+ mWakefulnessSessionObserver.updateSettingScreenOffTimeout(mContext);
+
+ try {
+ triggerLogSessionEvent();
+ verify(mWakefulnessSessionFrameworkStatsLogger, never())
+ .logSessionEvent(anyInt(), anyInt(), anyLong(), anyInt(), anyLong(), anyInt());
+ } finally {
+ // rollback the original data
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT);
+ mWakefulnessSessionObserver.updateSettingScreenOffTimeout(mContext);
+ }
+ }
+
+ @Test
+ public void testScreenOffTimeout_max_logSessionEventTriggered() {
+ // simulate adding the max screen_off_timeout value
+ int defaultTimeoutMs = Integer.MAX_VALUE;
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ defaultTimeoutMs,
+ UserHandle.USER_CURRENT);
+ mWakefulnessSessionObserver.updateSettingScreenOffTimeout(mContext);
+
+ try {
+ int powerGroupId = 1;
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY;
+ triggerLogSessionEvent(powerGroupId, userActivity);
+ verify(mWakefulnessSessionFrameworkStatsLogger)
+ .logSessionEvent(
+ powerGroupId, // powerGroupId
+ OFF_REASON_TIMEOUT, // interactiveStateOffReason
+ 0, // interactiveStateOnDurationMs
+ userActivity, // userActivity
+ 0, // lastUserActivityEventDurationMs
+ defaultTimeoutMs - OVERRIDE_SCREEN_OFF_TIMEOUT_MS
+ ); // reducedInteractiveStateOnDurationMs;
+ } finally {
+ // rollback the original data
+ Settings.System.putIntForUser(mContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT);
+ mWakefulnessSessionObserver.updateSettingScreenOffTimeout(mContext);
+ }
+ }
+
+ private void triggerLogSessionEvent() {
+ triggerLogSessionEvent(1, PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY);
+ }
+
+ private void triggerLogSessionEvent(int powerGroupId, int userActivity) {
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ powerGroupId,
+ PowerManagerInternal.WAKEFULNESS_AWAKE,
+ WAKE_REASON_POWER_BUTTON,
+ mTestClock.now());
+ mWakefulnessSessionObserver.onWakeLockAcquired(
+ PowerManager.SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK);
+
+ long userActivityTime = mTestClock.now();
+ mWakefulnessSessionObserver.notifyUserActivity(
+ userActivityTime, powerGroupId, userActivity);
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ powerGroupId,
+ PowerManagerInternal.WAKEFULNESS_DOZING,
+ GO_TO_SLEEP_REASON_TIMEOUT,
+ mTestClock.now());
+ }
+
private void advanceTime(long timeMs) {
mTestClock.fastForward(timeMs);
}
diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
new file mode 100644
index 000000000000..b1df0f1e9cce
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.test.FakePermissionEnforcer;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.security.advancedprotection.IAdvancedProtectionCallback;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@SuppressLint("VisibleForTests")
+@RunWith(JUnit4.class)
+public class AdvancedProtectionServiceTest {
+ private AdvancedProtectionService mService;
+ private FakePermissionEnforcer mPermissionEnforcer;
+ private Context mContext;
+ private AdvancedProtectionService.AdvancedProtectionStore mStore;
+ private TestLooper mLooper;
+ AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature("test-id");
+
+ @Before
+ public void setup() throws Settings.SettingNotFoundException {
+ mContext = mock(Context.class);
+ mPermissionEnforcer = new FakePermissionEnforcer();
+ mPermissionEnforcer.grant(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE);
+ mPermissionEnforcer.grant(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
+
+ mStore = new AdvancedProtectionService.AdvancedProtectionStore(mContext) {
+ private boolean mEnabled = false;
+
+ @Override
+ boolean retrieve() {
+ return mEnabled;
+ }
+
+ @Override
+ void store(boolean enabled) {
+ this.mEnabled = enabled;
+ }
+ };
+
+ mLooper = new TestLooper();
+
+ mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+ mPermissionEnforcer, null, null);
+ }
+
+ @Test
+ public void testToggleProtection() {
+ mService.setAdvancedProtectionEnabled(true);
+ assertTrue(mService.isAdvancedProtectionEnabled());
+
+ mService.setAdvancedProtectionEnabled(false);
+ assertFalse(mService.isAdvancedProtectionEnabled());
+ }
+
+ @Test
+ public void testDisableProtection_byDefault() {
+ assertFalse(mService.isAdvancedProtectionEnabled());
+ }
+
+ @Test
+ public void testEnableProtection_withHook() {
+ AtomicBoolean callbackCaptor = new AtomicBoolean(false);
+ AdvancedProtectionHook hook =
+ new AdvancedProtectionHook(mContext, true) {
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ callbackCaptor.set(enabled);
+ }
+ };
+
+ mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+ mPermissionEnforcer, hook, null);
+ mService.setAdvancedProtectionEnabled(true);
+ mLooper.dispatchNext();
+
+ assertTrue(callbackCaptor.get());
+ }
+
+ @Test
+ public void testEnableProtection_withFeature_notAvailable() {
+ AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
+ AdvancedProtectionHook hook =
+ new AdvancedProtectionHook(mContext, true) {
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ callbackCalledCaptor.set(true);
+ }
+ };
+
+ mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+ mPermissionEnforcer, hook, null);
+
+ mService.setAdvancedProtectionEnabled(true);
+ mLooper.dispatchNext();
+ assertFalse(callbackCalledCaptor.get());
+ }
+
+ @Test
+ public void testEnableProtection_withFeature_notCalledIfModeNotChanged() {
+ AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
+ AdvancedProtectionHook hook =
+ new AdvancedProtectionHook(mContext, true) {
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ callbackCalledCaptor.set(true);
+ }
+ };
+
+ mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+ mPermissionEnforcer, hook, null);
+ mService.setAdvancedProtectionEnabled(true);
+ mLooper.dispatchNext();
+ assertTrue(callbackCalledCaptor.get());
+
+ callbackCalledCaptor.set(false);
+ mService.setAdvancedProtectionEnabled(true);
+ mLooper.dispatchAll();
+ assertFalse(callbackCalledCaptor.get());
+ }
+
+ @Test
+ public void testRegisterCallback() throws RemoteException {
+ AtomicBoolean callbackCaptor = new AtomicBoolean(false);
+ IAdvancedProtectionCallback callback = new IAdvancedProtectionCallback.Stub() {
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ callbackCaptor.set(enabled);
+ }
+ };
+
+ mService.setAdvancedProtectionEnabled(true);
+ mLooper.dispatchAll();
+
+ mService.registerAdvancedProtectionCallback(callback);
+ mLooper.dispatchNext();
+ assertTrue(callbackCaptor.get());
+
+ mService.setAdvancedProtectionEnabled(false);
+ mLooper.dispatchNext();
+
+ assertFalse(callbackCaptor.get());
+ }
+
+ @Test
+ public void testUnregisterCallback() throws RemoteException {
+ AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
+ IAdvancedProtectionCallback callback = new IAdvancedProtectionCallback.Stub() {
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ callbackCalledCaptor.set(true);
+ }
+ };
+
+ mService.setAdvancedProtectionEnabled(true);
+ mService.registerAdvancedProtectionCallback(callback);
+ mLooper.dispatchAll();
+ callbackCalledCaptor.set(false);
+
+ mService.unregisterAdvancedProtectionCallback(callback);
+ mService.setAdvancedProtectionEnabled(false);
+
+ mLooper.dispatchNext();
+ assertFalse(callbackCalledCaptor.get());
+ }
+
+ @Test
+ public void testGetFeatures() {
+ AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
+ AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+ AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return feature1;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+ };
+
+ AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
+ @Override
+ public List<AdvancedProtectionFeature> getFeatures() {
+ return List.of(feature2);
+ }
+ };
+
+ mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+ mPermissionEnforcer, hook, provider);
+ List<AdvancedProtectionFeature> features = mService.getAdvancedProtectionFeatures();
+ assertThat(features, containsInAnyOrder(feature1, feature2));
+ }
+
+ @Test
+ public void testGetFeatures_featureNotAvailable() {
+ AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
+ AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+ AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return feature1;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+ };
+
+ AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
+ @Override
+ public List<AdvancedProtectionFeature> getFeatures() {
+ return List.of(feature2);
+ }
+ };
+
+ mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+ mPermissionEnforcer, hook, provider);
+ List<AdvancedProtectionFeature> features = mService.getAdvancedProtectionFeatures();
+ assertThat(features, containsInAnyOrder(feature2));
+ }
+
+
+ @Test
+ public void testSetProtection_withoutPermission() {
+ mPermissionEnforcer.revoke(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE);
+ assertThrows(SecurityException.class, () -> mService.setAdvancedProtectionEnabled(true));
+ }
+
+ @Test
+ public void testGetProtection_withoutPermission() {
+ mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
+ assertThrows(SecurityException.class, () -> mService.isAdvancedProtectionEnabled());
+ }
+
+ @Test
+ public void testRegisterCallback_withoutPermission() {
+ mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
+ assertThrows(SecurityException.class, () -> mService.registerAdvancedProtectionCallback(
+ new IAdvancedProtectionCallback.Default()));
+ }
+
+ @Test
+ public void testUnregisterCallback_withoutPermission() {
+ mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
+ assertThrows(SecurityException.class, () -> mService.unregisterAdvancedProtectionCallback(
+ new IAdvancedProtectionCallback.Default()));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
index d1806881ee37..ee8eb9b35088 100644
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-package com.android.server.adaptiveauth;
+package com.android.server.security.authenticationpolicy;
import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH;
import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+import static android.security.authenticationpolicy.AuthenticationPolicyManager.ERROR_UNSUPPORTED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
-import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
+import static com.android.server.security.authenticationpolicy.AuthenticationPolicyService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -66,12 +68,12 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * atest FrameworksServicesTests:AdaptiveAuthServiceTest
+ * atest FrameworksServicesTests:AuthenticationPolicyServiceTest
*/
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class AdaptiveAuthServiceTest {
+public class AuthenticationPolicyServiceTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -81,7 +83,7 @@ public class AdaptiveAuthServiceTest {
private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason
private Context mContext;
- private AdaptiveAuthService mAdaptiveAuthService;
+ private AuthenticationPolicyService mAuthenticationPolicyService;
@Mock
LockPatternUtils mLockPatternUtils;
@@ -95,6 +97,8 @@ public class AdaptiveAuthServiceTest {
private WindowManagerInternal mWindowManager;
@Mock
private UserManagerInternal mUserManager;
+ @Mock
+ private SecureLockDeviceServiceInternal mSecureLockDeviceService;
@Captor
ArgumentCaptor<LockSettingsStateListener> mLockSettingsStateListenerCaptor;
@@ -123,9 +127,15 @@ public class AdaptiveAuthServiceTest {
LocalServices.addService(WindowManagerInternal.class, mWindowManager);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mUserManager);
+ if (android.security.Flags.secureLockdown()) {
+ LocalServices.removeServiceForTest(SecureLockDeviceServiceInternal.class);
+ LocalServices.addService(SecureLockDeviceServiceInternal.class,
+ mSecureLockDeviceService);
+ }
- mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils);
- mAdaptiveAuthService.init();
+ mAuthenticationPolicyService = new AuthenticationPolicyService(
+ mContext, mLockPatternUtils);
+ mAuthenticationPolicyService.init();
verify(mLockSettings).registerLockSettingsStateListener(
mLockSettingsStateListenerCaptor.capture());
@@ -135,6 +145,12 @@ public class AdaptiveAuthServiceTest {
// Set PRIMARY_USER_ID as the parent of MANAGED_PROFILE_USER_ID
when(mUserManager.getProfileParentId(eq(MANAGED_PROFILE_USER_ID)))
.thenReturn(PRIMARY_USER_ID);
+ if (android.security.Flags.secureLockdown()) {
+ when(mSecureLockDeviceService.enableSecureLockDevice(any()))
+ .thenReturn(ERROR_UNSUPPORTED);
+ when(mSecureLockDeviceService.disableSecureLockDevice(any()))
+ .thenReturn(ERROR_UNSUPPORTED);
+ }
}
@After
@@ -142,6 +158,9 @@ public class AdaptiveAuthServiceTest {
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
+ if (android.security.Flags.secureLockdown()) {
+ LocalServices.removeServiceForTest(SecureLockDeviceServiceInternal.class);
+ }
}
@Test
@@ -317,13 +336,13 @@ public class AdaptiveAuthServiceTest {
private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) {
assertEquals(expectedCntFailedAttempts,
- mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+ mAuthenticationPolicyService.mFailedAttemptsForUser.get(userId));
verify(mWindowManager, never()).lockNow();
}
private void verifyLockDevice(int userId) {
assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS,
- mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+ mAuthenticationPolicyService.mFailedAttemptsForUser.get(userId));
verify(mLockPatternUtils).requireStrongAuth(
eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId));
// If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID)
diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/OWNERS b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/OWNERS
new file mode 100644
index 000000000000..4310d1a3a9db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/security/authenticationpolicy/OWNERS \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt b/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt
new file mode 100644
index 000000000000..8cf0e82259a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.netstats
+
+import android.net.NetworkStats
+import android.net.NetworkStats.DEFAULT_NETWORK_YES
+import android.net.NetworkStats.METERED_NO
+import android.net.NetworkStats.ROAMING_NO
+import android.net.NetworkStats.SET_DEFAULT
+import android.net.NetworkStats.TAG_NONE
+import android.net.NetworkTemplate
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.testutils.assertNetworkStatsEquals
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NetworkStatsAccumulatorTest {
+
+ @Test
+ fun hasEqualParameters_differentParameters_returnsFalse() {
+ val wifiTemplate = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
+ val mobileTemplate = NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE).build()
+
+ val snapshot = NetworkStatsAccumulator(wifiTemplate, false, 0, 0)
+
+ assertThat(snapshot.hasEqualParameters(mobileTemplate, false)).isFalse()
+ assertThat(snapshot.hasEqualParameters(wifiTemplate, true)).isFalse()
+ }
+
+ @Test
+ fun hasSameParameters_equalParameters_returnsTrue() {
+ val wifiTemplate1 = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
+ val wifiTemplate2 = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
+
+ val snapshot = NetworkStatsAccumulator(wifiTemplate1, false, 0, 0)
+
+ assertThat(snapshot.hasEqualParameters(wifiTemplate1, false)).isTrue()
+ assertThat(snapshot.hasEqualParameters(wifiTemplate2, false)).isTrue()
+ }
+
+ @Test
+ fun queryStats_lessThanOneBucketFromSnapshotEndTime_returnsAllStats() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)
+
+ // Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
+ // Current time is less than one bucket away from snapshot end-point: 1050 - 1000 < 200
+ val stats = snapshot.queryStats(1050, FakeStats(500, 1050, 1))
+
+ // After the query at 1050, accumulator should have 1 * (1050 - 1000) = 50 bytes.
+ assertNetworkStatsEquals(stats, networkStatsWithBytes(50))
+ }
+
+ @Test
+ fun queryStats_oneBucketFromSnapshotEndTime_returnsCumulativeStats() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)
+
+ // Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
+ // Current time is one bucket away from snapshot end-point: 1250 - 1000 > 200
+ val stats = snapshot.queryStats(1250, FakeStats(550, 1250, 2))
+
+ // After the query at 1250, accumulator should have 2 * (1250 - 1000) = 500 bytes.
+ assertNetworkStatsEquals(stats, networkStatsWithBytes(500))
+ }
+
+ @Test
+ fun queryStats_twoBucketsFromSnapshotEndTime_returnsCumulativeStats() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)
+
+ // Accumulator has data until 1000 (= 0), and its end-point is in the history period.
+ // Current time is two buckets away from snapshot end-point: 1450 - 1000 > 2*200
+ val stats = snapshot.queryStats(1450, FakeStats(600, 1450, 3))
+
+ // After the query at 1450, accumulator should have 3 * (1450 - 1000) = 1350 bytes.
+ assertNetworkStatsEquals(stats, networkStatsWithBytes(1350))
+ }
+
+ @Test
+ fun queryStats_manyBucketsFromSnapshotEndTime_returnsCumulativeStats() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)
+
+ // Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
+ // Current time is many buckets away from snapshot end-point
+ val stats = snapshot.queryStats(6100, FakeStats(900, 6100, 1))
+
+ // After the query at 6100, accumulator should have 1 * (6100 - 1000) = 5100 bytes.
+ assertNetworkStatsEquals(stats, networkStatsWithBytes(5100))
+ }
+
+ @Test
+ fun queryStats_multipleQueriesAndSameHistoryWindow_returnsCumulativeStats() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)
+
+ // Accumulator is queried within the history period, whose starting point stays the same.
+ // After each query, accumulator should contain bytes from the initial end-point until now.
+ val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))
+ val stats2 = snapshot.queryStats(10100, FakeStats(900, 10100, 1))
+ val stats3 = snapshot.queryStats(15100, FakeStats(900, 15100, 1))
+
+ assertNetworkStatsEquals(stats1, networkStatsWithBytes(4100))
+ assertNetworkStatsEquals(stats2, networkStatsWithBytes(9100))
+ assertNetworkStatsEquals(stats3, networkStatsWithBytes(14100))
+ }
+
+ @Test
+ fun queryStats_multipleQueriesAndSlidingHistoryWindow_returnsCumulativeStats() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1000)
+
+ // Accumulator is queried within the history period, whose starting point is moving.
+ // After each query, accumulator should contain bytes from the initial end-point until now.
+ val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))
+ val stats2 = snapshot.queryStats(10100, FakeStats(4000, 10100, 1))
+ val stats3 = snapshot.queryStats(15100, FakeStats(7000, 15100, 1))
+
+ assertNetworkStatsEquals(stats1, networkStatsWithBytes(4100))
+ assertNetworkStatsEquals(stats2, networkStatsWithBytes(9100))
+ assertNetworkStatsEquals(stats3, networkStatsWithBytes(14100))
+ }
+
+ @Test
+ fun queryStats_withSnapshotEndTimeBeforeHistoryStart_addsOnlyStatsWithinHistory() {
+ val snapshot = NetworkStatsAccumulator(TEMPLATE, false, 200, 1900)
+
+ // Accumulator has data until 1000 (= 0), but its end-point is not in the history period.
+ // After the query, accumulator should add only those bytes that are covered by the history.
+ val stats = snapshot.queryStats(2700, FakeStats(2200, 2700, 1))
+
+ assertNetworkStatsEquals(stats, networkStatsWithBytes(500))
+ }
+
+ /**
+ * Simulates equally distributed traffic stats persisted over a set period of time.
+ */
+ private class FakeStats(
+ val historyStartMillis: Long, val currentTimeMillis: Long, val bytesPerMilli: Long
+ ) : NetworkStatsAccumulator.StatsQueryFunction {
+
+ override fun queryNetworkStats(
+ template: NetworkTemplate, includeTags: Boolean, startTime: Long, endTime: Long
+ ): NetworkStats {
+ val overlap = overlap(startTime, endTime, historyStartMillis, currentTimeMillis)
+ return networkStatsWithBytes(overlap * bytesPerMilli)
+ }
+ }
+
+ companion object {
+
+ private val TEMPLATE = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
+
+ fun networkStatsWithBytes(bytes: Long): NetworkStats {
+ val stats = NetworkStats(0, 1).addEntry(
+ NetworkStats.Entry(
+ null,
+ 0,
+ SET_DEFAULT,
+ TAG_NONE,
+ METERED_NO,
+ ROAMING_NO,
+ DEFAULT_NETWORK_YES,
+ bytes,
+ bytes / 100,
+ bytes,
+ bytes / 100,
+ 0
+ )
+ )
+ return stats
+ }
+
+ fun overlap(aStart: Long, aEnd: Long, bStart: Long, bEnd: Long): Long {
+ return maxOf(0L, minOf(aEnd, bEnd) - maxOf(aStart, bStart))
+ }
+ }
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS b/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS
new file mode 100644
index 000000000000..e068a845c670
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/stats/pull/psi/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java
new file mode 100644
index 000000000000..b563c08f6be0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.psi;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+
+public class PsiExtractorTest {
+ @Mock
+ private PsiExtractor.PsiReader mPsiReader;
+ private PsiExtractor mPsiExtractor;
+ // PSI file content with both some and full lines.
+ private static final String PSI_FILE_CONTENT_BOTH_LINES =
+ "some avg10=0.12 avg60=0.34 avg300=0.56 total=12345678\n"
+ + "full avg10=0.21 avg60=0.43 avg300=0.65 total=87654321";
+ // PSI file content with only some line.
+ private static final String PSI_FILE_CONTENT_ONLY_SOME_LINE =
+ "some avg10=0.12 avg60=0.34 avg300=0.56 total=12345678";
+
+ // PSI file content with only full line.
+ private static final String PSI_FILE_CONTENT_ONLY_FULL_LINE =
+ "\nfull avg10=0.21 avg60=0.43 avg300=0.65 total=87654321";
+
+ // PSI file content that is malformed with "avg60" missing from the both lines.
+ private static final String BOTH_AVG60_MISSING_PSI_FILE_CONTENT =
+ "some avg10=0.12 avg300=0.56 total=12345678\n"
+ + "full avg10=0.21 avg300=0.65 total=87654321";
+
+ // PSI file content that is malformed with non number "avg10" from the both lines.
+ private static final String NON_NUM_AVG10_PSI_FILE_CONTENT =
+ "some avg10=1.a2 avg300=0.56 total=12345678\n"
+ + "full avg10=0.2s1 avg60=0.43 avg300=0.65 total=87654321";
+
+ // PSI file content that is malformed with non number "avg300" from the both lines.
+ private static final String NON_NUM_AVG300_PSI_FILE_CONTENT =
+ "some avg10=0.2 avg60=0.43 avg300=0.5ss6 total=12345678\n"
+ + "full avg10=0.21 avg60=0.43 avg300=0.6b5 total=87654321";
+
+ // PSI file content that is malformed with non number "total" from the both lines.
+ private static final String BOTH_TOTAL_MISSING_PSI_FILE_CONTENT =
+ "some avg10=0.2 avg60=0.43 avg300=0.56\n"
+ + "full avg10=0.21 avg60=0.43 avg300=0.65";
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mPsiExtractor = new PsiExtractor(mPsiReader);
+ }
+
+ @Test
+ public void getPsiData_bothLinesPresentedAndValidMemory() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ PSI_FILE_CONTENT_BOTH_LINES);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_bothLinesPresentedAndValidCpu() {
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ PSI_FILE_CONTENT_BOTH_LINES);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_bothLinesPresentedAndValidIO() {
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ PSI_FILE_CONTENT_BOTH_LINES);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_onlySomePresentedAndValidMemory() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_SOME_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullTotalUsec(), -1);
+ }
+
+ @Test
+ public void getPsiData_onlySomePresentedAndValidCpu() {
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_SOME_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.0);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.0);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.0);
+ assertEquals(psiData.getFullTotalUsec(), 0);
+ }
+
+ @Test
+ public void getPsiData_onlySomePresentedAndValidIO() {
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_SOME_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullTotalUsec(), -1);
+ }
+
+ @Test
+ public void getPsiData_onlyFullPresentedAndValidMemory() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_FULL_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeTotalUsec(), -1);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_onlyFullPresentedAndValidCpu() {
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_FULL_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeTotalUsec(), -1);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_onlyFullPresentedAndValidIO() {
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_FULL_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeTotalUsec(), -1);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_emptyFile() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn("");
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn("");
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn("");
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_avg60Missing() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_totalMissing() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_avg10NonNum() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ NON_NUM_AVG10_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ NON_NUM_AVG10_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ NON_NUM_AVG10_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_avg300NonNum() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ NON_NUM_AVG300_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ NON_NUM_AVG300_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ NON_NUM_AVG300_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
new file mode 100644
index 000000000000..5862ac65eba9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.supervision
+
+import android.app.Activity
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManagerInternal
+import android.app.supervision.flags.Flags
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.UserInfo
+import android.os.Handler
+import android.os.PersistableBundle
+import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
+import com.android.server.LocalServices
+import com.android.server.SystemService.TargetUser
+import com.android.server.pm.UserManagerInternal
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
+
+/**
+ * Unit tests for [SupervisionService].
+ *
+ * Run with `atest SupervisionServiceTest`.
+ */
+@RunWith(AndroidJUnit4::class)
+class SupervisionServiceTest {
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+ @Mock private lateinit var mockPackageManager: PackageManager
+ @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
+
+ private lateinit var context: Context
+ private lateinit var lifecycle: SupervisionService.Lifecycle
+ private lateinit var service: SupervisionService
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().context
+ context = SupervisionContextWrapper(context, mockPackageManager)
+
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
+ LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
+
+ LocalServices.removeServiceForTest(UserManagerInternal::class.java)
+ LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)
+
+ service = SupervisionService(context)
+ lifecycle = SupervisionService.Lifecycle(context, service)
+ lifecycle.registerProfileOwnerListener()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_supervisionAppIsProfileOwner_enablesSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
+
+ simulateUserStarting(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_userPreCreated_doesNotEnableSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
+
+ simulateUserStarting(USER_ID, preCreated = true)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName("other.package", "MainActivity"))
+
+ simulateUserStarting(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_supervisionAppIsProfileOwner_enablesSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
+
+ broadcastProfileOwnerChanged(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName("other.package", "MainActivity"))
+
+ broadcastProfileOwnerChanged(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID))
+ .thenReturn(arrayOf(systemSupervisionPackage))
+ service.setSupervisionEnabledForUser(USER_ID, true)
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID))
+ .thenReturn(arrayOf(systemSupervisionPackage))
+ service.setSupervisionEnabledForUser(USER_ID, false)
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_notSupervisionUid_returnsFalse() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID)).thenReturn(arrayOf())
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+ }
+
+ @Test
+ fun setSupervisionEnabledForUser() {
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+
+ service.setSupervisionEnabledForUser(USER_ID, true)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+
+ service.setSupervisionEnabledForUser(USER_ID, false)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun supervisionEnabledForUser_internal() {
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, false)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun setSupervisionLockscreenEnabledForUser() {
+ var userData = service.getUserDataLocked(USER_ID)
+ assertThat(userData.supervisionLockScreenEnabled).isFalse()
+ assertThat(userData.supervisionLockScreenOptions).isNull()
+
+ service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, true, PersistableBundle())
+ userData = service.getUserDataLocked(USER_ID)
+ assertThat(userData.supervisionLockScreenEnabled).isTrue()
+ assertThat(userData.supervisionLockScreenOptions).isNotNull()
+
+ service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, false, null)
+ userData = service.getUserDataLocked(USER_ID)
+ assertThat(userData.supervisionLockScreenEnabled).isFalse()
+ assertThat(userData.supervisionLockScreenOptions).isNull()
+ }
+
+ private val systemSupervisionPackage: String
+ get() = context.getResources().getString(R.string.config_systemSupervision)
+
+ private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) {
+ val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
+ userInfo.preCreated = preCreated
+ lifecycle.onUserStarting(TargetUser(userInfo))
+ }
+
+ private fun broadcastProfileOwnerChanged(userId: Int) {
+ val intent = Intent(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED)
+ context.sendBroadcastAsUser(intent, UserHandle.of(userId))
+ }
+
+ private companion object {
+ const val USER_ID = 100
+ val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
+ }
+}
+
+/**
+ * A context wrapper that allows broadcast intents to immediately invoke the receivers without
+ * performing checks on the sending user.
+ */
+private class SupervisionContextWrapper(val context: Context, val pkgManager: PackageManager) :
+ ContextWrapper(context) {
+ val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
+
+ override fun getPackageManager() = pkgManager
+
+ override fun registerReceiverForAllUsers(
+ receiver: BroadcastReceiver?,
+ filter: IntentFilter,
+ broadcastPermission: String?,
+ scheduler: Handler?,
+ ): Intent? {
+ if (receiver != null) {
+ interceptors.add(Pair(receiver, filter))
+ }
+ return null
+ }
+
+ override fun sendBroadcastAsUser(intent: Intent, user: UserHandle) {
+ val pendingResult =
+ BroadcastReceiver.PendingResult(
+ Activity.RESULT_OK,
+ /* resultData= */ "",
+ /* resultExtras= */ null,
+ /* type= */ 0,
+ /* ordered= */ true,
+ /* sticky= */ false,
+ /* token= */ null,
+ user.identifier,
+ /* flags= */ 0,
+ )
+ for ((receiver, filter) in interceptors) {
+ if (filter.match(contentResolver, intent, false, "") > 0) {
+ receiver.setPendingResult(pendingResult)
+ receiver.onReceive(context, intent)
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index bf58443194e5..d20f73d3c834 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.tv.tunerresourcemanager;
+import static android.media.tv.flags.Flags.FLAG_SET_RESOURCE_HOLDER_RETAIN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
@@ -39,7 +41,9 @@ import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -47,6 +51,7 @@ import androidx.test.filters.SmallTest;
import com.google.common.truth.Correspondence;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -70,6 +75,8 @@ public class TunerResourceManagerServiceTest {
private TunerResourceManagerService mTunerResourceManagerService;
private boolean mIsForeground;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final class TunerClient extends IResourcesReclaimListener.Stub {
int[] mClientId;
ClientProfile mProfile;
@@ -125,19 +132,6 @@ public class TunerResourceManagerServiceTest {
}
}
- private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub {
- boolean mReclaimed;
-
- @Override
- public void onReclaimResources() {
- mReclaimed = true;
- }
-
- public boolean isReclaimed() {
- return mReclaimed;
- }
- }
-
// A correspondence to compare a FrontendResource and a TunerFrontendInfo.
private static final Correspondence<FrontendResource, TunerFrontendInfo> FR_TFI_COMPARE =
Correspondence.from((FrontendResource actual, TunerFrontendInfo expected) -> {
@@ -175,8 +169,7 @@ public class TunerResourceManagerServiceTest {
tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- Map<Integer, FrontendResource> resources =
- mTunerResourceManagerService.getFrontendResources();
+ Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
for (int id = 0; id < infos.length; id++) {
assertThat(resources.get(infos[id].handle)
.getExclusiveGroupMemberFeHandles().size()).isEqualTo(0);
@@ -203,15 +196,14 @@ public class TunerResourceManagerServiceTest {
tunerFrontendInfo(3 /*handle*/, FrontendSettings.TYPE_ATSC, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- Map<Integer, FrontendResource> resources =
- mTunerResourceManagerService.getFrontendResources();
+ Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE)
.containsExactlyElementsIn(Arrays.asList(infos));
- assertThat(resources.get(0).getExclusiveGroupMemberFeHandles()).isEmpty();
- assertThat(resources.get(1).getExclusiveGroupMemberFeHandles()).containsExactly(2, 3);
- assertThat(resources.get(2).getExclusiveGroupMemberFeHandles()).containsExactly(1, 3);
- assertThat(resources.get(3).getExclusiveGroupMemberFeHandles()).containsExactly(1, 2);
+ assertThat(resources.get(0L).getExclusiveGroupMemberFeHandles()).isEmpty();
+ assertThat(resources.get(1L).getExclusiveGroupMemberFeHandles()).containsExactly(2L, 3L);
+ assertThat(resources.get(2L).getExclusiveGroupMemberFeHandles()).containsExactly(1L, 3L);
+ assertThat(resources.get(3L).getExclusiveGroupMemberFeHandles()).containsExactly(1L, 2L);
}
@Test
@@ -224,11 +216,11 @@ public class TunerResourceManagerServiceTest {
tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- Map<Integer, FrontendResource> resources0 =
+ Map<Long, FrontendResource> resources0 =
mTunerResourceManagerService.getFrontendResources();
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- Map<Integer, FrontendResource> resources1 =
+ Map<Long, FrontendResource> resources1 =
mTunerResourceManagerService.getFrontendResources();
assertThat(resources0).isEqualTo(resources1);
@@ -251,8 +243,7 @@ public class TunerResourceManagerServiceTest {
tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos1);
- Map<Integer, FrontendResource> resources =
- mTunerResourceManagerService.getFrontendResources();
+ Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
for (int id = 0; id < infos1.length; id++) {
assertThat(resources.get(infos1[id].handle)
.getExclusiveGroupMemberFeHandles().size()).isEqualTo(0);
@@ -278,8 +269,7 @@ public class TunerResourceManagerServiceTest {
tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos1);
- Map<Integer, FrontendResource> resources =
- mTunerResourceManagerService.getFrontendResources();
+ Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
for (int id = 0; id < infos1.length; id++) {
assertThat(resources.get(infos1[id].handle)
.getExclusiveGroupMemberFeHandles().size()).isEqualTo(0);
@@ -296,7 +286,7 @@ public class TunerResourceManagerServiceTest {
mTunerResourceManagerService.setFrontendInfoListInternal(infos0);
TunerFrontendRequest request =
tunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isFalse();
assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
@@ -317,7 +307,7 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isFalse();
assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
@@ -349,7 +339,7 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(0);
@@ -382,7 +372,7 @@ public class TunerResourceManagerServiceTest {
1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
TunerFrontendRequest request =
tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
assertThat(mTunerResourceManagerService
@@ -423,7 +413,7 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
@@ -463,12 +453,12 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
assertThat(client0.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(infos[0].handle, infos[1].handle)));
+ .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
request =
tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS);
@@ -489,6 +479,62 @@ public class TunerResourceManagerServiceTest {
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestFrontendTest_NoFrontendAvailable_RequestWithEqualPriority()
+ throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init frontend resource.
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[1];
+ infos[0] =
+ tunerFrontendInfo(0 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
+ mTunerResourceManagerService.setFrontendInfoListInternal(infos);
+
+ // client0 requests for 1 frontend
+ TunerFrontendRequest request =
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
+ long[] frontendHandle = new long[1];
+ assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
+ .isTrue();
+ assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
+ assertThat(client0.getProfile().getInUseFrontendHandles())
+ .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle)));
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
+
+ request = tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
+ assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
+ .isFalse();
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle).isInUse())
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
+ .getOwnerClientId())
+ .isEqualTo(client0.getId());
+ assertThat(client0.isReclaimed()).isFalse();
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceOwnershipRetention(false);
+
+ assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
+ .isTrue();
+ assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
+ .getOwnerClientId())
+ .isEqualTo(client1.getId());
+ assertThat(client0.isReclaimed()).isTrue();
+ }
+
+ @Test
public void releaseFrontendTest_UnderTheSameExclusiveGroup() throws RemoteException {
// Register clients
TunerClient client0 = new TunerClient();
@@ -505,7 +551,7 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
@@ -536,7 +582,7 @@ public class TunerResourceManagerServiceTest {
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
- int[] casSessionHandle = new int[1];
+ long[] casSessionHandle = new long[1];
// Request for 2 cas sessions.
assertThat(mTunerResourceManagerService
.requestCasSessionInternal(request, casSessionHandle)).isTrue();
@@ -569,6 +615,74 @@ public class TunerResourceManagerServiceTest {
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestCasTest_NoCasAvailable_RequestWithEqualPriority() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init cas resources.
+ mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+ CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
+ long[] casSessionHandle = new long[1];
+
+ // client0 requests for 2 cas sessions.
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isTrue();
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCasResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder to maintain ownership such as requester (client1) will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
+
+ request = casSessionRequest(client1.getId(), 1);
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isFalse();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(-1);
+ assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(client1.getProfile().getInUseCasSystemId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService.getCasResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+ assertThat(client0.isReclaimed()).isFalse();
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceOwnershipRetention(false);
+
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(client1.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCasResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client1.getId())));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+ assertThat(client0.isReclaimed()).isTrue();
+ }
+
+ @Test
public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority()
throws RemoteException {
// Register clients
@@ -583,7 +697,7 @@ public class TunerResourceManagerServiceTest {
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
- int[] ciCamHandle = new int[1];
+ long[] ciCamHandle = new long[1];
// Request for 2 ciCam sessions.
assertThat(mTunerResourceManagerService
.requestCiCamInternal(request, ciCamHandle)).isTrue();
@@ -616,6 +730,71 @@ public class TunerResourceManagerServiceTest {
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestCiCamTest_NoCiCamAvailable_RequestWithEqualPriority()
+ throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init cicam/cas resources.
+ mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+ TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
+ long[] ciCamHandle = new long[1];
+
+ // client0 request for 2 ciCam sessions.
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
+
+ request = tunerCiCamRequest(client1.getId(), 1);
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isFalse();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
+ .isEqualTo(-1);
+ assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(client1.getProfile().getInUseCiCamId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
+ assertThat(client0.isReclaimed()).isFalse();
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceOwnershipRetention(false);
+
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(client1.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client1.getId())));
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
+ assertThat(client0.isReclaimed()).isTrue();
+ }
+
+ @Test
public void releaseCasTest() throws RemoteException {
// Register clients
TunerClient client0 = new TunerClient();
@@ -626,7 +805,7 @@ public class TunerResourceManagerServiceTest {
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
- int[] casSessionHandle = new int[1];
+ long[] casSessionHandle = new long[1];
// Request for 1 cas sessions.
assertThat(mTunerResourceManagerService
.requestCasSessionInternal(request, casSessionHandle)).isTrue();
@@ -660,7 +839,7 @@ public class TunerResourceManagerServiceTest {
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
- int[] ciCamHandle = new int[1];
+ long[] ciCamHandle = new long[1];
// Request for 1 ciCam sessions.
assertThat(mTunerResourceManagerService
.requestCiCamInternal(request, ciCamHandle)).isTrue();
@@ -696,17 +875,17 @@ public class TunerResourceManagerServiceTest {
TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
// Init lnb resources.
- int[] lnbHandles = {1};
+ long[] lnbHandles = {1};
mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
TunerLnbRequest request = new TunerLnbRequest();
request.clientId = client0.getId();
- int[] lnbHandle = new int[1];
+ long[] lnbHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestLnbInternal(request, lnbHandle)).isTrue();
assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
assertThat(client0.getProfile().getInUseLnbHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(lnbHandles[0])));
+ .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
request = new TunerLnbRequest();
request.clientId = client1.getId();
@@ -725,6 +904,59 @@ public class TunerResourceManagerServiceTest {
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestLnbTest_NoLnbAvailable_RequestWithEqualPriority() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init lnb resources.
+ long[] lnbHandles = {1};
+ mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
+
+ // client0 requests 1 lnb
+ TunerLnbRequest request = new TunerLnbRequest();
+ request.clientId = client0.getId();
+ long[] lnbHandle = new long[1];
+ assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
+ assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
+ assertThat(client0.getProfile().getInUseLnbHandles())
+ .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
+
+ request = new TunerLnbRequest();
+ request.clientId = client1.getId();
+
+ assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isFalse();
+ assertThat(lnbHandle[0]).isNotEqualTo(lnbHandles[0]);
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).isInUse()).isTrue();
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).getOwnerClientId())
+ .isEqualTo(client0.getId());
+ assertThat(client0.isReclaimed()).isFalse();
+ assertThat(client1.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceOwnershipRetention(false);
+
+ assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
+ assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).isInUse()).isTrue();
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).getOwnerClientId())
+ .isEqualTo(client1.getId());
+ assertThat(client0.isReclaimed()).isTrue();
+ assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+ }
+
+ @Test
public void releaseLnbTest() throws RemoteException {
// Register clients
TunerClient client0 = new TunerClient();
@@ -732,12 +964,12 @@ public class TunerResourceManagerServiceTest {
TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
// Init lnb resources.
- int[] lnbHandles = {0};
+ long[] lnbHandles = {0};
mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
TunerLnbRequest request = new TunerLnbRequest();
request.clientId = client0.getId();
- int[] lnbHandle = new int[1];
+ long[] lnbHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestLnbInternal(request, lnbHandle)).isTrue();
assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
@@ -768,7 +1000,7 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
assertThat(mTunerResourceManagerService
.requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
@@ -799,12 +1031,12 @@ public class TunerResourceManagerServiceTest {
infos[2] = tunerDemuxInfo(2 /* handle */, Filter.TYPE_TS);
mTunerResourceManagerService.setDemuxInfoListInternal(infos);
- int[] demuxHandle0 = new int[1];
+ long[] demuxHandle0 = new long[1];
// first with undefined type (should be the first one with least # of caps)
TunerDemuxRequest request = tunerDemuxRequest(client0.getId(), Filter.TYPE_UNDEFINED);
assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
.isTrue();
- assertThat(demuxHandle0[0]).isEqualTo(1);
+ assertThat(demuxHandle0[0]).isEqualTo(1L);
DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
mTunerResourceManagerService.releaseDemuxInternal(dr);
@@ -813,20 +1045,20 @@ public class TunerResourceManagerServiceTest {
demuxHandle0[0] = -1;
assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
.isFalse();
- assertThat(demuxHandle0[0]).isEqualTo(-1);
+ assertThat(demuxHandle0[0]).isEqualTo(-1L);
// now with TS (should be the one with least # of caps that supports TS)
request.desiredFilterTypes = Filter.TYPE_TS;
assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
.isTrue();
- assertThat(demuxHandle0[0]).isEqualTo(2);
+ assertThat(demuxHandle0[0]).isEqualTo(2L);
// request for another TS
TunerClient client1 = new TunerClient();
client1.register("1" /*sessionId*/,
TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] demuxHandle1 = new int[1];
+ long[] demuxHandle1 = new long[1];
TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_TS);
assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1))
.isTrue();
@@ -865,14 +1097,14 @@ public class TunerResourceManagerServiceTest {
// let client0(prio:100) request for IP - should succeed
TunerDemuxRequest request0 = tunerDemuxRequest(client0.getId(), Filter.TYPE_IP);
- int[] demuxHandle0 = new int[1];
+ long[] demuxHandle0 = new long[1];
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request0, demuxHandle0)).isTrue();
assertThat(demuxHandle0[0]).isEqualTo(0);
// let client1(prio:50) request for IP - should fail
TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_IP);
- int[] demuxHandle1 = new int[1];
+ long[] demuxHandle1 = new long[1];
demuxHandle1[0] = -1;
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request1, demuxHandle1)).isFalse();
@@ -892,7 +1124,7 @@ public class TunerResourceManagerServiceTest {
// let client2(prio:50) request for TS - should succeed
TunerDemuxRequest request2 = tunerDemuxRequest(client2.getId(), Filter.TYPE_TS);
- int[] demuxHandle2 = new int[1];
+ long[] demuxHandle2 = new long[1];
assertThat(mTunerResourceManagerService
.requestDemuxInternal(request2, demuxHandle2)).isTrue();
assertThat(demuxHandle2[0]).isEqualTo(0);
@@ -917,7 +1149,7 @@ public class TunerResourceManagerServiceTest {
client0.register("0" /*sessionId*/,
TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
- int[] desHandle = new int[1];
+ long[] desHandle = new long[1];
TunerDescramblerRequest request = new TunerDescramblerRequest();
request.clientId = client0.getId();
assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
@@ -980,7 +1212,7 @@ public class TunerResourceManagerServiceTest {
1 /*exclusiveGroupId*/);
/**** Init Lnb Resources ****/
- int[] lnbHandles = {1};
+ long[] lnbHandles = {1};
mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
// Update frontend list in TRM
@@ -989,7 +1221,7 @@ public class TunerResourceManagerServiceTest {
/**** Request Frontend ****/
// Predefined frontend request and array to save returned frontend handle
- int[] frontendHandle = new int[1];
+ long[] frontendHandle = new long[1];
TunerFrontendRequest request = tunerFrontendRequest(
ownerClient0.getId() /*clientId*/,
FrontendSettings.TYPE_DVBT);
@@ -1000,7 +1232,7 @@ public class TunerResourceManagerServiceTest {
.isTrue();
assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
@@ -1030,15 +1262,15 @@ public class TunerResourceManagerServiceTest {
shareClient1.getId())));
// Verify in use frontend list in all the primary owner and share owner clients
assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
assertThat(shareClient0.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
assertThat(shareClient1.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
@@ -1052,12 +1284,12 @@ public class TunerResourceManagerServiceTest {
.isEqualTo(new HashSet<Integer>(Arrays.asList(
shareClient0.getId())));
assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
assertThat(shareClient0.getProfile()
.getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
@@ -1080,7 +1312,7 @@ public class TunerResourceManagerServiceTest {
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
.getOwnerClientId()).isEqualTo(ownerClient1.getId());
assertThat(ownerClient1.getProfile().getInUseFrontendHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
infos[0].handle,
infos[1].handle)));
assertThat(ownerClient0.getProfile().getInUseFrontendHandles()
@@ -1127,7 +1359,7 @@ public class TunerResourceManagerServiceTest {
// Predefined Lnb request and handle array
TunerLnbRequest requestLnb = new TunerLnbRequest();
requestLnb.clientId = shareClient0.getId();
- int[] lnbHandle = new int[1];
+ long[] lnbHandle = new long[1];
// Request for an Lnb
assertThat(mTunerResourceManagerService
@@ -1155,7 +1387,7 @@ public class TunerResourceManagerServiceTest {
.isEmpty())
.isTrue();
assertThat(shareClient0.getProfile().getInUseLnbHandles())
- .isEqualTo(new HashSet<Integer>(Arrays.asList(
+ .isEqualTo(new HashSet<Long>(Arrays.asList(
lnbHandles[0])));
ownerClient0.unregister();
@@ -1163,7 +1395,7 @@ public class TunerResourceManagerServiceTest {
}
private TunerFrontendInfo tunerFrontendInfo(
- int handle, int frontendType, int exclusiveGroupId) {
+ long handle, int frontendType, int exclusiveGroupId) {
TunerFrontendInfo info = new TunerFrontendInfo();
info.handle = handle;
info.type = frontendType;
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 89056cc34795..9a7abd43aab6 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -93,7 +93,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Pair;
@@ -111,6 +114,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -185,6 +189,9 @@ public class AppStandbyControllerTests {
private static final Random sRandom = new Random();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private MyInjector mInjector;
private AppStandbyController mController;
@@ -894,7 +901,8 @@ public class AppStandbyControllerTests {
}
@Test
- public void testScreenTimeAndBuckets() throws Exception {
+ @DisableFlags(Flags.FLAG_SCREEN_TIME_BYPASS)
+ public void testScreenTimeAndBuckets_Legacy() throws Exception {
mInjector.setDisplayOn(false);
assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
@@ -917,6 +925,31 @@ public class AppStandbyControllerTests {
}
@Test
+ @EnableFlags(Flags.FLAG_SCREEN_TIME_BYPASS)
+ public void testScreenTimeAndBuckets_Bypass() throws Exception {
+ mInjector.setDisplayOn(false);
+
+ assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
+
+ reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+
+ // ACTIVE bucket
+ assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE);
+
+ // WORKING_SET bucket
+ assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET);
+
+ // RARE bucket, should failed due to timeout hasn't reached yet.
+ mInjector.mElapsedRealtime = RARE_THRESHOLD - 1;
+ mController.checkIdleStates(USER_ID);
+ waitAndAssertNotBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+
+ mInjector.setDisplayOn(true);
+ // screen on time doesn't count.
+ assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_RARE);
+ }
+
+ @Test
public void testForcedIdle() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index def33551a820..13a6e4cc7b30 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
public class TestSystemImpl implements SystemInterface {
private String mUserProvider = null;
@@ -36,18 +37,17 @@ public class TestSystemImpl implements SystemInterface {
Map<String, Map<Integer, PackageInfo>> mPackages = new HashMap();
private final int mNumRelros;
private final boolean mIsDebuggable;
- private int mMultiProcessSetting;
- private final boolean mMultiProcessDefault;
+ private Predicate<PackageInfo> mCompatibilityPredicate;
public static final int PRIMARY_USER_ID = 0;
- public TestSystemImpl(WebViewProviderInfo[] packageConfigs, int numRelros, boolean isDebuggable,
- boolean multiProcessDefault) {
+ public TestSystemImpl(WebViewProviderInfo[] packageConfigs, int numRelros,
+ boolean isDebuggable) {
mPackageConfigs = packageConfigs;
mNumRelros = numRelros;
mIsDebuggable = isDebuggable;
mUsers.add(PRIMARY_USER_ID);
- mMultiProcessDefault = multiProcessDefault;
+ mCompatibilityPredicate = pi -> true;
}
public void addUser(int userId) {
@@ -132,6 +132,15 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
+ public boolean isCompatibleImplementationPackage(PackageInfo packageInfo) {
+ return mCompatibilityPredicate.test(packageInfo);
+ }
+
+ public void setCompatibilityPredicate(Predicate<PackageInfo> predicate) {
+ mCompatibilityPredicate = predicate;
+ }
+
+ @Override
public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo info) {
Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
List<UserPackage> ret = new ArrayList();
@@ -181,26 +190,8 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
- public int getMultiProcessSetting() {
- return mMultiProcessSetting;
- }
-
- @Override
- public void setMultiProcessSetting(int value) {
- mMultiProcessSetting = value;
- }
-
- @Override
- public void notifyZygote(boolean enableMultiProcess) {}
-
- @Override
public void ensureZygoteStarted() {}
@Override
- public boolean isMultiProcessDefaultEnabled() {
- return mMultiProcessDefault;
- }
-
- @Override
public void pinWebviewIfRequired(ApplicationInfo appInfo) {}
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 06479c84bfc7..bf99b6af2345 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -16,8 +16,6 @@
package com.android.server.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -32,7 +30,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Base64;
-import android.webkit.UserPackage;
+import android.webkit.Flags;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -62,7 +60,7 @@ import java.util.concurrent.CountDownLatch;
public class WebViewUpdateServiceTest {
private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName();
- private WebViewUpdateServiceInterface mWebViewUpdateServiceImpl;
+ private WebViewUpdateServiceImpl2 mWebViewUpdateServiceImpl;
private TestSystemImpl mTestSystemImpl;
private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary";
@@ -77,38 +75,23 @@ public class WebViewUpdateServiceTest {
}
private void setupWithPackages(WebViewProviderInfo[] packages) {
- setupWithAllParameters(packages, 1 /* numRelros */, true /* isDebuggable */,
- false /* multiProcessDefault */);
+ setupWithAllParameters(packages, 1 /* numRelros */, true /* isDebuggable */);
}
private void setupWithPackagesAndRelroCount(WebViewProviderInfo[] packages, int numRelros) {
- setupWithAllParameters(packages, numRelros, true /* isDebuggable */,
- false /* multiProcessDefault */);
+ setupWithAllParameters(packages, numRelros, true /* isDebuggable */);
}
private void setupWithPackagesNonDebuggable(WebViewProviderInfo[] packages) {
- setupWithAllParameters(packages, 1 /* numRelros */, false /* isDebuggable */,
- false /* multiProcessDefault */);
- }
-
- private void setupWithPackagesAndMultiProcess(WebViewProviderInfo[] packages,
- boolean multiProcessDefault) {
- setupWithAllParameters(packages, 1 /* numRelros */, true /* isDebuggable */,
- multiProcessDefault);
+ setupWithAllParameters(packages, 1 /* numRelros */, false /* isDebuggable */);
}
private void setupWithAllParameters(WebViewProviderInfo[] packages, int numRelros,
- boolean isDebuggable, boolean multiProcessDefault) {
- TestSystemImpl testing = new TestSystemImpl(packages, numRelros, isDebuggable,
- multiProcessDefault);
+ boolean isDebuggable) {
+ TestSystemImpl testing = new TestSystemImpl(packages, numRelros, isDebuggable);
mTestSystemImpl = Mockito.spy(testing);
- if (updateServiceV2()) {
- mWebViewUpdateServiceImpl =
- new WebViewUpdateServiceImpl2(mTestSystemImpl);
- } else {
- mWebViewUpdateServiceImpl =
- new WebViewUpdateServiceImpl(mTestSystemImpl);
- }
+ mWebViewUpdateServiceImpl =
+ new WebViewUpdateServiceImpl2(mTestSystemImpl);
}
private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) {
@@ -194,8 +177,6 @@ public class WebViewUpdateServiceTest {
// no flag means invalid
p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah");
}
- // Default to this package being valid in terms of targetSdkVersion.
- p.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
return p;
}
@@ -350,24 +331,6 @@ public class WebViewUpdateServiceTest {
}
@Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, will throw an exception because of no available by default provider.
- public void testEmptyConfig() {
- WebViewProviderInfo[] packages = new WebViewProviderInfo[0];
- setupWithPackages(packages);
- setEnabledAndValidPackageInfos(packages);
-
- runWebViewBootPreparationOnMainSync();
-
- Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
- Matchers.anyObject());
-
- WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
- assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
- assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
- }
-
- @Test
public void testFailListingEmptyWebviewPackages() {
String singlePackage = "singlePackage";
WebViewProviderInfo[] packages = new WebViewProviderInfo[]{
@@ -554,73 +517,6 @@ public class WebViewUpdateServiceTest {
}
}
- /**
- * Scenario for testing re-enabling a fallback package.
- */
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testFallbackPackageEnabling() {
- String testPackage = "testFallback";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- testPackage, "", true /* default available */, true /* fallback */, null)};
- setupWithPackages(packages);
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(testPackage, false /* enabled */ , true /* valid */,
- true /* installed */));
-
- // Check that the boot time logic re-enables the fallback package.
- runWebViewBootPreparationOnMainSync();
- Mockito.verify(mTestSystemImpl).enablePackageForAllUsers(
- Mockito.eq(testPackage), Mockito.eq(true));
-
- // Fake the message about the enabling having changed the package state,
- // and check we now use that package.
- mWebViewUpdateServiceImpl.packageStateChanged(
- testPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
- checkPreparationPhasesForPackage(testPackage, 1);
- }
-
- /**
- * Scenario for installing primary package when secondary in use.
- * 1. Start with only secondary installed
- * 2. Install primary
- * 3. Primary should be used
- */
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, we don't automitally switch to secondary package unless it is
- // chosen directly.
- public void testInstallingPrimaryPackage() {
- String primaryPackage = "primary";
- String secondaryPackage = "secondary";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- primaryPackage, "", true /* default available */, false /* fallback */, null),
- new WebViewProviderInfo(
- secondaryPackage, "", true /* default available */, false /* fallback */,
- null)};
- setupWithPackages(packages);
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(secondaryPackage, true /* enabled */ , true /* valid */,
- true /* installed */));
-
- runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(secondaryPackage,
- 1 /* first preparation for this package*/);
-
- // Install primary package
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */,
- true /* installed */));
- mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
-
- // Verify primary package used as provider, and secondary package killed
- checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation for this package*/);
- Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(secondaryPackage));
- }
-
@Test
public void testRemovingSecondarySelectsPrimarySingleUser() {
for (PackageRemovalType removalType : REMOVAL_TYPES) {
@@ -848,14 +744,6 @@ public class WebViewUpdateServiceTest {
checkRecoverAfterFailListingWebviewPackages(true);
}
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, we don't automitally switch to second package unless it is chosen
- // directly.
- public void testRecoverFailedListingWebViewPackagesAddedPackage() {
- checkRecoverAfterFailListingWebviewPackages(false);
- }
-
/**
* Test that we can recover correctly from failing to list WebView packages.
* settingsChange: whether to fail during changeProviderAndSetting or packageStateChanged
@@ -1114,31 +1002,6 @@ public class WebViewUpdateServiceTest {
}
}
- /**
- * Ensure that the update service does not use an uninstalled package even if it is the only
- * package available.
- */
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, we return the package even if it is not installed.
- public void testWithSingleUninstalledPackage() {
- String testPackageName = "test.package.name";
- WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(testPackageName, "",
- true /*default available*/, false /* fallback */, null)};
- setupWithPackages(webviewPackages);
- mTestSystemImpl.setPackageInfo(createPackageInfo(testPackageName, true /* enabled */,
- true /* valid */, false /* installed */));
-
- runWebViewBootPreparationOnMainSync();
-
- Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
- Matchers.anyObject());
- WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
- assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
- assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
- }
-
@Test
public void testNonhiddenPackageUserOverHidden() {
checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.HIDE);
@@ -1374,110 +1237,22 @@ public class WebViewUpdateServiceTest {
mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName);
}
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessEnabledByDefault() {
- testMultiProcessByDefault(true /* enabledByDefault */);
- }
-
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessDisabledByDefault() {
- testMultiProcessByDefault(false /* enabledByDefault */);
- }
-
- private void testMultiProcessByDefault(boolean enabledByDefault) {
- String primaryPackage = "primary";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- primaryPackage, "", true /* default available */, false /* fallback */, null)};
- setupWithPackagesAndMultiProcess(packages, enabledByDefault);
- mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
- true /* valid */, true /* installed */, null /* signatures */,
- 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
- false /* isSystemApp */));
-
- runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
-
- // Check it's off by default
- assertEquals(enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
-
- // Test toggling it
- mWebViewUpdateServiceImpl.enableMultiProcess(!enabledByDefault);
- assertEquals(!enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
- mWebViewUpdateServiceImpl.enableMultiProcess(enabledByDefault);
- assertEquals(enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
- }
-
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessEnabledByDefaultWithSettingsValue() {
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, -999999, true /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, 0, true /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, 999999, true /* expectEnabled */);
- }
-
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessDisabledByDefaultWithSettingsValue() {
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, 0, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, 999999, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, Integer.MAX_VALUE, true /* expectEnabled */);
- }
-
/**
- * Test the logic of the multiprocess setting depending on whether multiprocess is enabled by
- * default, and what the setting is set to.
- * @param enabledByDefault whether multiprocess is enabled by default.
- * @param settingValue value of the multiprocess setting.
- */
- private void testMultiProcessByDefaultWithSettingsValue(
- boolean enabledByDefault, int settingValue, boolean expectEnabled) {
- String primaryPackage = "primary";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- primaryPackage, "", true /* default available */, false /* fallback */, null)};
- setupWithPackagesAndMultiProcess(packages, enabledByDefault);
- mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
- true /* valid */, true /* installed */, null /* signatures */,
- 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
- false /* isSystemApp */));
-
- runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
-
- mTestSystemImpl.setMultiProcessSetting(settingValue);
-
- assertEquals(expectEnabled, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
- }
-
-
- /**
- * Ensure that packages with a targetSdkVersion targeting the current platform are valid, and
+ * Ensure that packages with a targetSdkVersion targeting the correct platform are valid, and
* that packages targeting an older version are not valid.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_USE_B_ENTRY_POINT)
public void testTargetSdkVersionValidity() {
PackageInfo newSdkPackage = createPackageInfo("newTargetSdkPackage",
- true /* enabled */, true /* valid */, true /* installed */);
- newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ true /* enabled */, true /* valid */, true /* installed */);
+ newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
PackageInfo currentSdkPackage = createPackageInfo("currentTargetSdkPackage",
- true /* enabled */, true /* valid */, true /* installed */);
- currentSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK;
+ true /* enabled */, true /* valid */, true /* installed */);
+ currentSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
PackageInfo oldSdkPackage = createPackageInfo("oldTargetSdkPackage",
- true /* enabled */, true /* valid */, true /* installed */);
- oldSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK - 1;
+ true /* enabled */, true /* valid */, true /* installed */);
+ oldSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.S;
WebViewProviderInfo newSdkProviderInfo =
new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
@@ -1490,6 +1265,9 @@ public class WebViewUpdateServiceTest {
newSdkProviderInfo
};
setupWithPackages(packages);
+ // Mock the compatibility predicate, requiring T as targetSdkVersion.
+ mTestSystemImpl.setCompatibilityPredicate(
+ pi -> pi.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU);
// Start with the setting pointing to the invalid package
mTestSystemImpl.updateUserSetting(oldSdkPackage.packageName);
@@ -1506,8 +1284,55 @@ public class WebViewUpdateServiceTest {
1 /* first preparation phase */);
}
+ /**
+ * Ensure that packages with a versionCode new enough for the current platform are valid, and
+ * that older packages are not valid.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_USE_B_ENTRY_POINT)
+ public void testVersionCodeOSCompatValidity() {
+ PackageInfo newVersionPackage = createPackageInfo("newVersionPackage",
+ true /* enabled */, true /* valid */, true /* installed */);
+ newVersionPackage.setLongVersionCode(200L);
+ PackageInfo currentVersionPackage = createPackageInfo("currentVersionPackage",
+ true /* enabled */, true /* valid */, true /* installed */);
+ currentVersionPackage.setLongVersionCode(100L);
+ PackageInfo oldVersionPackage = createPackageInfo("oldVersionPackage",
+ true /* enabled */, true /* valid */, true /* installed */);
+ oldVersionPackage.setLongVersionCode(50L);
+
+ WebViewProviderInfo newVersionProviderInfo =
+ new WebViewProviderInfo(newVersionPackage.packageName, "", true, false, null);
+ WebViewProviderInfo currentVersionProviderInfo =
+ new WebViewProviderInfo(currentVersionPackage.packageName, "", true, false, null);
+ WebViewProviderInfo[] packages =
+ new WebViewProviderInfo[] {
+ currentVersionProviderInfo,
+ new WebViewProviderInfo(oldVersionPackage.packageName, "", true, false, null),
+ newVersionProviderInfo
+ };
+ setupWithPackages(packages);
+ // Mock the compatibility predicate as requiring 100 as versionCode.
+ mTestSystemImpl.setCompatibilityPredicate(
+ pi -> pi.getLongVersionCode() >= 100L);
+ // Start with the setting pointing to the invalid package
+ mTestSystemImpl.updateUserSetting(oldVersionPackage.packageName);
+
+ mTestSystemImpl.setPackageInfo(newVersionPackage);
+ mTestSystemImpl.setPackageInfo(currentVersionPackage);
+ mTestSystemImpl.setPackageInfo(oldVersionPackage);
+
+ assertArrayEquals(
+ new WebViewProviderInfo[] { currentVersionProviderInfo, newVersionProviderInfo },
+ mWebViewUpdateServiceImpl.getValidWebViewPackages());
+
+ runWebViewBootPreparationOnMainSync();
+
+ checkPreparationPhasesForPackage(currentVersionPackage.packageName,
+ 1 /* first preparation phase */);
+ }
+
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() {
String nonDefaultPackage = "nonDefaultPackage";
String defaultPackage1 = "defaultPackage1";
@@ -1524,7 +1349,6 @@ public class WebViewUpdateServiceTest {
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageEnabling() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1548,7 +1372,6 @@ public class WebViewUpdateServiceTest {
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageInstallingDuringStartUp() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1572,7 +1395,6 @@ public class WebViewUpdateServiceTest {
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageInstallingAfterStartUp() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1603,7 +1425,6 @@ public class WebViewUpdateServiceTest {
* the repair logic.
*/
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testAddingNewUserWithDefaultdPackageNotInstalled() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1651,7 +1472,6 @@ public class WebViewUpdateServiceTest {
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDisabledDefaultPackageChosen() {
PackageInfo disabledPackage =
createPackageInfo(
@@ -1664,7 +1484,6 @@ public class WebViewUpdateServiceTest {
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testUninstalledDefaultPackageChosen() {
PackageInfo uninstalledPackage =
createPackageInfo(
diff --git a/services/tests/timetests/Android.bp b/services/tests/timetests/Android.bp
index aae6acc7c53a..65a694e7c5fa 100644
--- a/services/tests/timetests/Android.bp
+++ b/services/tests/timetests/Android.bp
@@ -19,6 +19,7 @@ android_test {
"platform-test-annotations",
"services.core",
"truth",
+ "ApplicationSharedMemoryTestRule",
],
libs: ["android.test.runner.stubs.system"],
platform_apis: true,
diff --git a/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index c64ec724b641..3836063a1b44 100644
--- a/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -46,6 +46,7 @@ import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
+import com.android.internal.os.ApplicationSharedMemoryTestRule;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.StateChangeListener;
@@ -55,6 +56,7 @@ import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -125,6 +127,10 @@ public class TimeDetectorStrategyImplTest {
.setAutoDetectionEnabledSetting(true)
.build();
+ @Rule
+ public final ApplicationSharedMemoryTestRule mApplicationSharedMemoryTestRule =
+ new ApplicationSharedMemoryTestRule();
+
private FakeEnvironment mFakeEnvironment;
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private TimeDetectorStrategyImpl mTimeDetectorStrategy;
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 3b0cb4ad8779..c4b8599a483c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -118,6 +118,7 @@ import org.mockito.Spy;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -232,7 +233,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
any(AlarmManager.OnAlarmListener.class), any(Handler.class));
doAnswer(inv -> {
- mCustomListener = () -> {};
+ mCustomListener = () -> {
+ };
return null;
}).when(mAlarmManager).cancel(eq(mCustomListener));
when(mContext.getSystemService(eq(Context.POWER_SERVICE)))
@@ -1321,7 +1323,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void enableCarMode_failsForBogusPackageName() throws Exception {
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
assertThrows(SecurityException.class, () -> mService.enableCarMode(0, 0, PACKAGE_NAME));
assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
@@ -1343,19 +1345,19 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void disableCarMode_failsForBogusPackageName() throws Exception {
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID);
mService.enableCarMode(0, 0, PACKAGE_NAME);
assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR);
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
assertThrows(SecurityException.class,
- () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME));
+ () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME));
assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR);
// Clean up
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID);
mService.disableCarModeByCallingPackage(0, PACKAGE_NAME);
assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
}
@@ -1460,9 +1462,12 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
- private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+ // Test the attention mode overlay with all the possible attention modes and the initial night
+ // mode state. Also tests if the attention mode is turned off when the night mode is toggled by
+ // the user.
+ private void testAttentionModeThemeOverlay(boolean initialNightMode) throws RemoteException {
//setup
- if (modeNight) {
+ if (initialNightMode) {
mService.setNightMode(MODE_NIGHT_YES);
assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
} else {
@@ -1470,23 +1475,32 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
}
- // attention modes with expected night modes
- Map<Integer, Boolean> modes = Map.of(
- MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
- MODE_ATTENTION_THEME_OVERLAY_DAY, false,
- MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
- );
+ // Attention modes with expected night modes.
+ // Important to keep modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode) in the
+ // first position, hence LinkedHashMap.
+ Map<Integer, Boolean> modes = new LinkedHashMap<>();
+ modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode);
+ modes.put(MODE_ATTENTION_THEME_OVERLAY_DAY, false);
+ modes.put(MODE_ATTENTION_THEME_OVERLAY_NIGHT, true);
// test
- for (int aMode : modes.keySet()) {
+ for (int attentionMode : modes.keySet()) {
try {
- mService.setAttentionModeThemeOverlay(aMode);
+ mService.setAttentionModeThemeOverlay(attentionMode);
int appliedAMode = mService.getAttentionModeThemeOverlay();
- boolean nMode = modes.get(aMode);
-
- assertEquals(aMode, appliedAMode);
- assertEquals(isNightModeActivated(), nMode);
+ boolean expectedNightMode = modes.get(attentionMode);
+
+ assertEquals(attentionMode, appliedAMode);
+ assertEquals(expectedNightMode, isNightModeActivated());
+
+ // If attentionMode is active, flip the night mode and assets
+ // the attention mode is disabled
+ if (attentionMode != MODE_ATTENTION_THEME_OVERLAY_OFF) {
+ mService.setNightModeActivated(!expectedNightMode);
+ assertEquals(MODE_ATTENTION_THEME_OVERLAY_OFF,
+ mService.getAttentionModeThemeOverlay());
+ }
} catch (RemoteException e) {
fail("Error communicating with server: " + e.getMessage());
}
@@ -1551,7 +1565,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
private final int callingUid;
public TestInjector() {
- this(DEFAULT_CALLING_UID);
+ this(DEFAULT_CALLING_UID);
}
public TestInjector(int callingUid) {
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index c9d5241c57b7..b3ec2153542a 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -30,7 +30,6 @@ import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.After;
@@ -42,7 +41,6 @@ import org.mockito.MockitoAnnotations;
public class UiServiceTestCase {
@Mock protected PackageManagerInternal mPmi;
- @Mock protected UserManagerInternal mUmi;
@Mock protected UriGrantsManagerInternal mUgmInternal;
protected static final String PKG_N_MR1 = "com.example.n_mr1";
@@ -94,8 +92,6 @@ public class UiServiceTestCase {
}
});
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mUmi);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
when(mUgmInternal.checkGrantUriPermission(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
index 4af96ef4800f..fa1372d9f4ef 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
@@ -19,13 +19,25 @@ package com.android.server.notification;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.app.Application;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -35,16 +47,24 @@ import com.android.server.UiServiceTestCase;
import com.android.server.pm.PackageManagerService;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@RunWithLooper
public class EventConditionProviderTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- EventConditionProvider mService;
+ private EventConditionProvider mService;
+ @Mock private UserManager mUserManager;
@Before
public void setUp() throws Exception {
@@ -65,6 +85,18 @@ public class EventConditionProviderTest extends UiServiceTestCase {
service.onCreate();
service.onBind(startIntent);
mService = spy(service);
+ mService.mContext = this.getContext();
+
+ mContext.addMockSystemService(UserManager.class, mUserManager);
+ when(mUserManager.getProfiles(eq(mUserId))).thenReturn(
+ List.of(new UserInfo(mUserId, "mUserId", 0)));
+
+ doAnswer((Answer<Context>) invocationOnMock -> {
+ Context mockUserContext = mock(Context.class);
+ UserHandle userHandle = invocationOnMock.getArgument(2);
+ when(mockUserContext.getUserId()).thenReturn(userHandle.getIdentifier());
+ return mockUserContext;
+ }).when(mContext).createPackageContextAsUser(any(), anyInt(), any());
}
@Test
@@ -78,4 +110,72 @@ public class EventConditionProviderTest extends UiServiceTestCase {
PendingIntent pi = mService.getPendingIntent(1000);
assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
}
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onBootComplete_flagOff_loadsTrackers() {
+ when(mUserManager.getUserProfiles()).thenReturn(List.of(UserHandle.of(mUserId)));
+
+ mService.onBootComplete();
+
+ assertThat(mService.getTrackers().size()).isEqualTo(1);
+ assertThat(mService.getTrackers().keyAt(0)).isEqualTo(mUserId);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onBootComplete_loadsTrackersForSystemUser() {
+ mService.onBootComplete();
+
+ assertThat(mService.getTrackers().size()).isEqualTo(1);
+ assertThat(mService.getTrackers().keyAt(0)).isEqualTo(UserHandle.USER_SYSTEM);
+ assertThat(mService.getTrackers().valueAt(0).getUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onUserSwitched_reloadsTrackers() {
+ UserHandle someUser = UserHandle.of(42);
+ when(mUserManager.getProfiles(eq(42))).thenReturn(List.of(new UserInfo(42, "user 42", 0)));
+
+ mService.onUserSwitched(someUser);
+
+ assertThat(mService.getTrackers().size()).isEqualTo(1);
+ assertThat(mService.getTrackers().keyAt(0)).isEqualTo(42);
+ assertThat(mService.getTrackers().valueAt(0).getUserId()).isEqualTo(42);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onUserSwitched_reloadsTrackersIncludingProfiles() {
+ UserHandle anotherUser = UserHandle.of(42);
+ when(mUserManager.getProfiles(eq(42))).thenReturn(List.of(
+ new UserInfo(42, "user 42", 0),
+ new UserInfo(43, "profile 43", 0)));
+
+ mService.onUserSwitched(anotherUser);
+
+ assertThat(mService.getTrackers().size()).isEqualTo(2);
+ assertThat(mService.getTrackers().keyAt(0)).isEqualTo(42);
+ assertThat(mService.getTrackers().valueAt(0).getUserId()).isEqualTo(42);
+ assertThat(mService.getTrackers().keyAt(1)).isEqualTo(43);
+ assertThat(mService.getTrackers().valueAt(1).getUserId()).isEqualTo(43);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onUserSwitched_sameUser_doesNothing() {
+ UserHandle someUser = UserHandle.of(42);
+ when(mUserManager.getProfiles(eq(42))).thenReturn(List.of(new UserInfo(42, "user 42", 0)));
+
+ mService.onUserSwitched(someUser);
+ assertThat(mService.getTrackers().size()).isEqualTo(1);
+ assertThat(mService.getTrackers().keyAt(0)).isEqualTo(42);
+ CalendarTracker originalTracker = mService.getTrackers().valueAt(0);
+
+ mService.onUserSwitched(someUser);
+ assertThat(mService.getTrackers().size()).isEqualTo(1);
+ assertThat(mService.getTrackers().keyAt(0)).isEqualTo(42);
+ assertThat(mService.getTrackers().valueAt(0)).isSameInstanceAs(originalTracker);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 585df84f7f90..6cb24293a7d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -34,10 +34,12 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS;
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS;
import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.GroupHelper.BASE_FLAGS;
@@ -82,7 +84,9 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.GroupHelper.CachedSummary;
+import com.android.server.notification.GroupHelper.FullyQualifiedGroupKey;
import com.android.server.notification.GroupHelper.NotificationAttributes;
+import com.android.server.notification.GroupHelper.NotificationSectioner;
import org.junit.Before;
import org.junit.Rule;
@@ -2166,6 +2170,345 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testRemoveChildNotification_summaryForceGrouped() {
+ // Check that removing all child notifications from a group will trigger empty summary
+ // force grouping re-evaluation
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ // Post summaries without children, below the force grouping limit
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // Post a valid (full) group
+ final int summaryId = 4242;
+ final int numChildren = 3;
+ final ArrayList<NotificationRecord> childrenToRemove = new ArrayList<>();
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId, true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, summaryId + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + summaryId, false);
+ notificationList.add(child);
+ // schedule all children for removal
+ childrenToRemove.add(child);
+ }
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+
+ // Remove all child notifications from the valid group => summary without children
+ Mockito.reset(mCallback);
+ for (NotificationRecord r: childrenToRemove) {
+ notificationList.remove(r);
+ mGroupHelper.onNotificationRemoved(r, notificationList);
+ }
+ // Only call onGroupedNotificationRemovedWithDelay with the summary notification
+ mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
+ summaryByGroup);
+
+ // Check that the summaries were force grouped
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testRemoveChildNotification_groupBecomesSingleton() {
+ // Check that removing child notifications from a group will trigger singleton force
+ // grouping re-evaluation
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ // Post singleton groups, under forced group limit
+ for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT - 1; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i,
+ String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(child);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // Post a valid (full) group
+ final int summaryId = 4242;
+ final int numChildren = 3;
+ final ArrayList<NotificationRecord> childrenToRemove = new ArrayList<>();
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId, true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, summaryId + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + summaryId, false);
+ notificationList.add(child);
+
+ // schedule all children except one for removal
+ if (i < numChildren - 1) {
+ childrenToRemove.add(child);
+ }
+ }
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+
+ // Remove some child notifications from the valid group, transform into a singleton group
+ Mockito.reset(mCallback);
+ for (NotificationRecord r: childrenToRemove) {
+ notificationList.remove(r);
+ mGroupHelper.onNotificationRemoved(r, notificationList);
+ }
+ // Only call onGroupedNotificationRemovedWithDelay with the summary notification
+ mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
+ summaryByGroup);
+
+ // Check that the singleton groups were force grouped
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary(
+ anyString());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testRemoveAllGroupNotifications_noForceGrouping() {
+ // Check that removing all notifications from a group will not trigger any force grouping
+ // re-evaluation
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ // Post summaries without children, below the force grouping limit
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // Post a valid (full) group
+ final int summaryId = 4242;
+ final int numChildren = 3;
+ final String groupToRemove = "testRemoveGrp";
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, groupToRemove + summaryId, true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, summaryId + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, groupToRemove + summaryId, false);
+ notificationList.add(child);
+ }
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+
+ // Remove all child notifications from the valid group => summary without children
+ Mockito.reset(mCallback);
+ for (NotificationRecord r: notificationList) {
+ if (r.getGroupKey().contains(groupToRemove)) {
+ r.isCanceled = true;
+ mGroupHelper.onNotificationRemoved(r, notificationList);
+ }
+ }
+ // Only call onGroupedNotificationRemovedWithDelay with the summary notification
+ mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
+ summaryByGroup);
+ // Check that nothing was force grouped
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testUpdateToUngroupableSection_cleanupUngrouped() {
+ final String pkg = "package";
+ // Post notification w/o group in a valid section
+ NotificationRecord notification = spy(getNotificationRecord(pkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, 0, "0", UserHandle.SYSTEM));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ mGroupHelper.onNotificationPosted(notification, false);
+
+ // Update notification to invalid section
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification)).isNull();
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notification, false);
+ assertThat(needsAutogrouping).isFalse();
+
+ // Check that GH internal state (ungrouped list) was cleaned-up
+ // Post AUTOGROUP_AT_COUNT-1 notifications => should not autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ int id = 42 + i;
+ notification = getNotificationRecord(pkg, id, "" + id, mUser,
+ null, false, IMPORTANCE_LOW);
+ mGroupHelper.onNotificationPosted(notification, false);
+ }
+
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testUpdateToUngroupableSection_afterAutogroup_isUngrouped() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post notification w/o group in a valid section
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = spy(getNotificationRecord(pkg, i, "" + i, mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ mGroupHelper.onNotificationPosted(notification, false);
+ notificationList.add(notification);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to invalid section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+ true);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, true);
+ assertThat(needsAutogrouping).isFalse();
+
+ // Check that the updated notification was removed from the autogroup
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testUpdateToUngroupableSection_onRemoved_isUngrouped() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post notification w/o group in a valid section
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = spy(getNotificationRecord(pkg, i, "" + i, mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ mGroupHelper.onNotificationPosted(notification, false);
+ notificationList.add(notification);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to invalid section and removed it
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+ true);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+ notificationList.remove(notifToInvalidate);
+ mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
+
+ // Check that the autogroup was updated
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testUpdateToUngroupableSection_afterForceGrouping_isUngrouped() {
+ final String pkg = "package";
+ final String groupName = "testGroup";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post valid section summary notifications without children => force group
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = spy(getNotificationRecord(mPkg, i, "" + i, mUser,
+ groupName, true, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM, groupName));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(n.getGroup()).thenReturn(groupName);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+ summaryByGroup);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to invalid section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+ true);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, true);
+ assertThat(needsAutogrouping).isFalse();
+
+ // Check that GH internal state (ungrouped list) was cleaned-up
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel() {
final String pkg = "package";
@@ -2217,6 +2560,7 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION)
public void testMoveAggregateGroups_updateChannel_multipleChannels() {
final String pkg = "package";
final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
@@ -2265,16 +2609,17 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
notificationList);
- // Check that channel1's notifications are moved to the silent section group
- // But not enough to auto-group => remove override group key
- verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- anyString(), anyInt(), any());
- verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ // Check that the override group key was cleared
for (NotificationRecord record: notificationList) {
if (record.getChannel().getId().equals(channel1.getId())) {
assertThat(record.getSbn().getOverrideGroupKey()).isNull();
}
}
+ // Check that channel1's notifications are moved to the silent section group
+ // and a group summary is created + notifications are added to the group
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), anyString(),
+ anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
// Check that the alerting section group is not removed, only updated
expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -2287,6 +2632,624 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() {
+ final String pkg = "package";
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final int numNotifications = 2 * AUTOGROUP_AT_COUNT;
+ int numNotificationChannel1 = 0;
+ final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+ final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_DEFAULT);
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post notifications with different channels that autogroup within the same section
+ NotificationRecord r;
+ for (int i = 0; i < numNotifications; i++) {
+ if (i % 2 == 0) {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, channel1);
+ numNotificationChannel1++;
+ } else {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, channel2);
+ }
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+ mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ "TEST_CHANNEL_ID1");
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), eq(expectedSummaryAttr));
+ verify(mCallback, times(numNotifications)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_alerting), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(numNotifications - AUTOGROUP_AT_COUNT)).updateAutogroupSummary(
+ anyInt(), anyString(), anyString(), any());
+ Mockito.reset(mCallback);
+
+ // Update channel1's importance
+ final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ channel1.setImportance(IMPORTANCE_LOW);
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())) {
+ record.updateNotificationChannel(channel1);
+ }
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
+ notificationList);
+
+ // Check that the override group key was cleared
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())) {
+ assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+ }
+ }
+ // Check that channel1's notifications are moved to the silent section group
+ // and a group summary is created + notifications are added to the group
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_silent), anyInt(), any());
+ verify(mCallback, times(numNotificationChannel1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_silent), anyBoolean());
+
+ // Check that the alerting section group is not removed, only updated
+ expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+ mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ "TEST_CHANNEL_ID2");
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting), eq(expectedSummaryAttr));
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testMoveSections_notificationBundled() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final String pkg = "package";
+ final int summaryId = 0;
+ final int numChildNotif = 2 * AUTOGROUP_AT_COUNT;
+
+ // Create an app-provided group: summary + child notifications
+ final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId,
+ true, channel1);
+ notificationList.add(summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ for (int i = 0; i < numChildNotif; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp " + summaryId, false, channel1);
+ notificationList.add(child);
+ }
+
+ // Classify/bundle child notifications
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ final NotificationChannel newsChannel = new NotificationChannel(
+ NotificationChannel.NEWS_ID, NotificationChannel.NEWS_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_news = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "NewsSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_news = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.NEWS_ID);
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()
+ && record.getSbn().getId() % 2 == 0) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()
+ && record.getSbn().getId() % 2 != 0) {
+ record.updateNotificationChannel(newsChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ }
+
+ // Check that 2 autogroup summaries were created for the news & social sections
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_news), anyInt(), eq(expectedSummaryAttr_news));
+ // Check that half of the child notifications were grouped in each new section
+ verify(mCallback, times(numChildNotif / 2)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_news), eq(true));
+ verify(mCallback, times(numChildNotif / 2)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(numChildNotif)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testCacheAndCancelAppSummary_notificationBundled() {
+ // check that the original app summary is canceled & cached on classification regrouping
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final String pkg = "package";
+ final int summaryId = 0;
+ final int numChildNotif = 4;
+
+ // Create an app-provided group: summary + child notifications
+ final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId,
+ true, channel1);
+ notificationList.add(summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final String originalAppGroupName = summary.getNotification().getGroup();
+ for (int i = 0; i < numChildNotif; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp " + summaryId, false, channel1);
+ notificationList.add(child);
+ }
+
+ // Last regrouped notification will trigger summary cancellation in NMS
+ when(mCallback.removeAppProvidedSummaryOnClassification(anyString(),
+ eq(originalAppGroupKey))).thenReturn(summary);
+
+ // Classify/bundle child notifications
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ }
+
+ // Check that the original app summary was cached
+ CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg,
+ String.valueOf(summaryId), summaryId, UserHandle.SYSTEM.getIdentifier());
+ assertThat(cachedSummary.originalGroupKey()).isEqualTo(originalAppGroupName);
+ assertThat(cachedSummary.key()).isEqualTo(summary.getKey());
+
+ // App cancels the original summary
+ reset(mCallback);
+ mGroupHelper.maybeCancelGroupChildrenForCanceledSummary(pkg, String.valueOf(summaryId),
+ summaryId, UserHandle.SYSTEM.getIdentifier(), REASON_APP_CANCEL);
+ // Check that child notifications are removed and cache is cleared
+ verify(mCallback, times(1)).removeNotificationFromCanceledGroup(
+ eq(UserHandle.SYSTEM.getIdentifier()), eq(pkg), eq(originalAppGroupName),
+ eq(REASON_APP_CANCEL));
+ cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(summaryId), summaryId,
+ UserHandle.SYSTEM.getIdentifier());
+ assertThat(cachedSummary).isNull();
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() {
+ // Check that singleton group notifications are regrouped if classification is done
+ // before onNotificationPostedWithDelay
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ final int numChildNotifications = AUTOGROUP_AT_COUNT;
+
+ // Post singleton groups, above forced group limit
+ for (int i = 0; i < numChildNotifications; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i,
+ String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(child);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ }
+
+ // Classify/bundle child notifications
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ }
+
+ // Check that notifications are forced grouped and app-provided summaries are canceled
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(numChildNotifications)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(numChildNotifications)).removeAppProvidedSummaryOnClassification(
+ anyString(), anyString());
+
+ // Adjust group key and cancel summaries
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupSummary()) {
+ record.isCanceled = true;
+ } else {
+ record.setOverrideGroupKey(expectedGroupKey_social);
+ }
+ }
+
+ // Check that after onNotificationPostedWithDelay there is no change in the grouping
+ reset(mCallback);
+ for (NotificationRecord record: notificationList) {
+ mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup);
+ }
+
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() {
+ // Check that singleton group notifications are regrouped if classification is done
+ // after onNotificationPostedWithDelay
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ String expectedTriggeringKey = null;
+ // Post singleton groups, above forced group limit
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i,
+ String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(child);
+ if (i == AUTOGROUP_SINGLETONS_AT_COUNT - 1) {
+ expectedTriggeringKey = child.getKey();
+ }
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ summary.isCanceled = true; // simulate removing the app summary
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+
+ // Check that notifications are forced grouped and app-provided summaries are canceled
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedTriggeringKey), eq(expectedGroupKey_alerting), anyInt(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(0), 0,
+ UserHandle.SYSTEM.getIdentifier())).isNotNull();
+ assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(1), 1,
+ UserHandle.SYSTEM.getIdentifier())).isNotNull();
+
+ // Classify/bundle child notifications
+ reset(mCallback);
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ }
+
+ // Check that all notifications are moved to the social section group
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ // Check that the alerting section group is removed
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).updateAutogroupSummary(anyInt(),
+ anyString(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testValidGroupsRegrouped_notificationBundledWhileEnqueued() {
+ // Check that valid group notifications are regrouped if classification is done
+ // before onNotificationPostedWithDelay (within DELAY_FOR_ASSISTANT_TIME)
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ }
+
+ // Classify/bundle child notifications. Don't call onChannelUpdated,
+ // adjustments applied while enqueued will use NotificationAdjustmentExtractor.
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ }
+ }
+
+ // Check that notifications are forced grouped and app-provided summaries are canceled
+ for (NotificationRecord record: notificationList) {
+ mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup);
+ }
+
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(numChildren)).addAutoGroup(anyString(), eq(expectedGroupKey_social),
+ eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(),
+ anyString());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testUnbundleNotification_originalSummaryMissing_autogroupInNewSection() {
+ // Check that unbundled notifications are moved to the original section and aggregated
+ // with existing autogrouped notifications
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT - 1;
+ // Post a regular/valid group: summary + notifications (one less than autogroup limit)
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify/bundle all child notifications: original group & summary is removed
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ }
+
+ // Check that no autogroup summaries were created for the social section
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+
+ // Cancel summary
+ summary.isCanceled = true;
+ summaryByGroup.clear();
+ notificationList.remove(summary);
+
+ // Add 1 ungrouped notification in the original section
+ NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+ String.valueOf(4242), UserHandle.SYSTEM);
+ notificationList.add(ungroupedNotification);
+ mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+ // Unbundle the bundled notifications => notifications are moved back to the original group
+ // and an aggregate group is created because autogroup limit is reached
+ reset(mCallback);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(originalChannel);
+ mGroupHelper.onNotificationUnbundled(record, false);
+ }
+ }
+
+ // Check that a new aggregate group is created
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_alerting), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(numChildren)).removeAutoGroupSummary(anyInt(), anyString(),
+ anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testUnbundleNotification_originalSummaryExists() {
+ // Check that unbundled notifications are moved to the original section and original group
+ // when the original summary is still present
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT + 1;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify/bundle child notifications: all except one, to keep the original group
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ int numChildrenBundled = 0;
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ numChildrenBundled++;
+ if (numChildrenBundled == AUTOGROUP_AT_COUNT) {
+ break;
+ }
+ }
+ }
+
+ // Check that 1 autogroup summaries were created for the social section
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+
+ // Adjust group key and cancel summaries
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupSummary()) {
+ record.isCanceled = true;
+ } else if (record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.setOverrideGroupKey(expectedGroupKey_social);
+ }
+ }
+
+ // Add 1 ungrouped notification in the original section
+ NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+ String.valueOf(4242), UserHandle.SYSTEM);
+ notificationList.add(ungroupedNotification);
+ mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+ // Unbundle the bundled notifications => social section summary is destroyed
+ // and notifications are moved back to the original group
+ reset(mCallback);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(originalChannel);
+ mGroupHelper.onNotificationUnbundled(record, true);
+ }
+ }
+
+ // Check that the autogroup summary for the social section was removed
+ // and that no new autogroup summaries were created
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_social), any());
+
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")) {
+ assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+ }
+ }
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
final String pkg = "package";
@@ -2633,6 +3596,120 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
@DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
+ public void testNonGroupableChildren_singletonGroups_disableConversations() {
+ // Check that singleton groups with children that are not groupable, is not grouped
+ // Even though the group summary is a regular (alerting) notification, the children are
+ // conversations => the group should not be forced grouped.
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ // Trigger notification, ungrouped
+ final int triggerId = 1;
+ NotificationRecord triggerNotification = getNotificationRecord(pkg, triggerId,
+ String.valueOf(triggerId), UserHandle.SYSTEM);
+ notificationList.add(triggerNotification);
+ final NotificationSectioner triggerSection = GroupHelper.getSection(triggerNotification);
+ final FullyQualifiedGroupKey triggerFullAggregateGroupKey = new FullyQualifiedGroupKey(
+ triggerNotification.getUserId(), triggerNotification.getSbn().getPackageName(),
+ triggerSection);
+
+ // Add singleton group with alerting child
+ final String groupName_valid = "testGrp_valid";
+ final int summaryId_valid = 0;
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId_valid,
+ String.valueOf(summaryId_valid), UserHandle.SYSTEM, groupName_valid, true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String groupKey_valid = summary.getGroupKey();
+ NotificationRecord child = getNotificationRecord(pkg, summaryId_valid + 42,
+ String.valueOf(summaryId_valid + 42), UserHandle.SYSTEM, groupName_valid, false);
+ notificationList.add(child);
+
+ // Add singleton group with conversation child
+ final String groupName_invalid = "testGrp_invalid";
+ final int summaryId_invalid = 100;
+ summary = getNotificationRecord(pkg, summaryId_invalid,
+ String.valueOf(summaryId_invalid), UserHandle.SYSTEM, groupName_invalid, true);
+ notificationList.add(summary);
+ final String groupKey_invalid = summary.getGroupKey();
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ child = getNotificationRecord(pkg, summaryId_invalid + 42,
+ String.valueOf(summaryId_invalid + 42), UserHandle.SYSTEM, groupName_invalid,
+ false);
+ child = spy(child);
+ when(child.isConversation()).thenReturn(true);
+ notificationList.add(child);
+
+ // Check that the invalid group will not be force grouped
+ final ArrayMap<String, NotificationRecord> sparseGroups = mGroupHelper.getSparseGroups(
+ triggerFullAggregateGroupKey, notificationList, summaryByGroup, triggerSection);
+ assertThat(sparseGroups).containsKey(groupKey_valid);
+ assertThat(sparseGroups).doesNotContainKey(groupKey_invalid);
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+ public void testNonGroupableChildren_singletonGroups_enableConversations() {
+ // Check that singleton groups with children that are not groupable, is not grouped
+ // Conversations are groupable (FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS is enabled)
+ // The invalid group is the alerting notifications: because the triggering notifications'
+ // section is Conversations, so the alerting group should be skipped.
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ // Trigger notification, ungrouped conversation
+ final int triggerId = 1;
+ NotificationRecord triggerNotification = getNotificationRecord(pkg, triggerId,
+ String.valueOf(triggerId), UserHandle.SYSTEM);
+ triggerNotification = spy(triggerNotification);
+ when(triggerNotification.isConversation()).thenReturn(true);
+ notificationList.add(triggerNotification);
+ final NotificationSectioner triggerSection = GroupHelper.getSection(triggerNotification);
+ final FullyQualifiedGroupKey triggerFullAggregateGroupKey = new FullyQualifiedGroupKey(
+ triggerNotification.getUserId(), triggerNotification.getSbn().getPackageName(),
+ triggerSection);
+
+ // Add singleton group with conversation child
+ final String groupName_valid = "testGrp_valid";
+ final int summaryId_valid = 0;
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId_valid,
+ String.valueOf(summaryId_valid), UserHandle.SYSTEM, groupName_valid, true);
+ summary = spy(summary);
+ when(summary.isConversation()).thenReturn(true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String groupKey_valid = summary.getGroupKey();
+ NotificationRecord child = getNotificationRecord(pkg, summaryId_valid + 42,
+ String.valueOf(summaryId_valid + 42), UserHandle.SYSTEM, groupName_valid, false);
+ child = spy(child);
+ when(child.isConversation()).thenReturn(true);
+ notificationList.add(child);
+
+ // Add singleton group with non-conversation child
+ final String groupName_invalid = "testGrp_invalid";
+ final int summaryId_invalid = 100;
+ summary = getNotificationRecord(pkg, summaryId_invalid,
+ String.valueOf(summaryId_invalid), UserHandle.SYSTEM, groupName_invalid, true);
+ notificationList.add(summary);
+ final String groupKey_invalid = summary.getGroupKey();
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ child = getNotificationRecord(pkg, summaryId_invalid + 42,
+ String.valueOf(summaryId_invalid + 42), UserHandle.SYSTEM, groupName_invalid,
+ false);
+ notificationList.add(child);
+
+ // Check that the invalid group will not be force grouped
+ final ArrayMap<String, NotificationRecord> sparseGroups = mGroupHelper.getSparseGroups(
+ triggerFullAggregateGroupKey, notificationList, summaryByGroup, triggerSection);
+ assertThat(sparseGroups).containsKey(groupKey_valid);
+ assertThat(sparseGroups).doesNotContainKey(groupKey_invalid);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
public void testNonGroupableNotifications() {
// Check that there is no valid section for: conversations, calls, foreground services
NotificationRecord notification_conversation = mock(NotificationRecord.class);
@@ -2662,6 +3739,17 @@ public class GroupHelperTest extends UiServiceTestCase {
when(n.isColorized()).thenReturn(true);
when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+
+ NotificationRecord notification_media = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ n = mock(Notification.class);
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_media.isConversation()).thenReturn(false);
+ when(notification_media.getNotification()).thenReturn(n);
+ when(notification_media.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isMediaNotification()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_media)).isNull();
}
@Test
@@ -2756,7 +3844,7 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
public void testNonGroupableNotifications_forceGroupConversations() {
- // Check that there is no valid section for: calls, foreground services
+ // Check that there is no valid section for: calls, foreground services, media notifications
NotificationRecord notification_call = spy(getNotificationRecord(mPkg, 0, "", mUser,
"", false, IMPORTANCE_LOW));
Notification n = mock(Notification.class);
@@ -2780,6 +3868,17 @@ public class GroupHelperTest extends UiServiceTestCase {
when(n.isColorized()).thenReturn(true);
when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+
+ NotificationRecord notification_media = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ n = mock(Notification.class);
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_media.isConversation()).thenReturn(false);
+ when(notification_media.getNotification()).thenReturn(n);
+ when(notification_media.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isMediaNotification()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_media)).isNull();
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 48bc9d7c51a1..e5c42082ab97 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1484,7 +1484,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(componentsToUnbind.get(0).contains(ComponentName.unflattenFromString("c/c")));
}
- @SuppressWarnings("GuardedBy")
@Test
public void populateComponentsToBind() {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
@@ -1508,8 +1507,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
- service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
- /* isVisibleBackgroundUser= */ false);
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
assertEquals(2, componentsToBind.size());
assertEquals(1, componentsToBind.get(0).size());
@@ -1519,33 +1517,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c")));
}
- @SuppressWarnings("GuardedBy")
- @Test
- public void populateComponentsToBind_isVisibleBackgroundUser_addComponentsToBindButNotAddToEnabledComponent() {
- ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
- APPROVAL_BY_COMPONENT);
-
- SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
- ArraySet<ComponentName> allowed = new ArraySet<>();
- allowed.add(ComponentName.unflattenFromString("pkg1/cmp1"));
- approvedComponentsByUser.put(11, allowed);
- IntArray users = new IntArray();
- users.add(11);
-
- SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
-
- service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
- /* isVisibleBackgroundUser= */ true);
-
- assertEquals(1, componentsToBind.size());
- assertEquals(1, componentsToBind.get(11).size());
- assertTrue(componentsToBind.get(11).contains(ComponentName.unflattenFromString(
- "pkg1/cmp1")));
- assertThat(service.isComponentEnabledForCurrentProfiles(
- new ComponentName("pkg1", "cmp1"))).isFalse();
- assertThat(service.isComponentEnabledForPackage("pkg1")).isFalse();
- }
-
@Test
public void testOnNullBinding() throws Exception {
Context context = mock(Context.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index 18ca09be235c..bf0586ceb32d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -18,11 +18,21 @@ package com.android.server.notification;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -30,12 +40,16 @@ import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.Adjustment;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import com.android.server.UiServiceTestCase;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -43,6 +57,9 @@ import java.util.Objects;
public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Test
public void testExtractsAdjustment() {
NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
@@ -111,6 +128,44 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
}
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testClassificationAdjustments_triggerRegrouping() {
+ GroupHelper groupHelper = mock(GroupHelper.class);
+ NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+ extractor.setGroupHelper(groupHelper);
+
+ NotificationRecord r = generateRecord();
+
+ Bundle classificationAdj = new Bundle();
+ classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+ r.addAdjustment(adjustment);
+
+ RankingReconsideration regroupingTask = extractor.process(r);
+ assertThat(regroupingTask).isNotNull();
+ regroupingTask.applyChangesLocked(r);
+ verify(groupHelper, times(1)).onChannelUpdated(r);
+ }
+
+ @Test
+ @DisableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testClassificationAdjustments_notTriggerRegrouping_flagsDisabled() {
+ GroupHelper groupHelper = mock(GroupHelper.class);
+ NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+ extractor.setGroupHelper(groupHelper);
+
+ NotificationRecord r = generateRecord();
+
+ Bundle classificationAdj = new Bundle();
+ classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+ r.addAdjustment(adjustment);
+
+ RankingReconsideration regroupingTask = extractor.process(r);
+ assertThat(regroupingTask).isNull();
+ }
+
private NotificationRecord generateRecord() {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification.Builder builder = new Notification.Builder(getContext())
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index a45b102278ef..decbaacdcef9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -15,12 +15,21 @@
*/
package com.android.server.notification;
+import static android.os.UserHandle.USER_ALL;
+import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+
+import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +42,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.content.ComponentName;
@@ -42,24 +52,28 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
-import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.Adjustment;
import android.testing.TestableContext;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Xml;
-import android.Manifest;
+
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -71,8 +85,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+@RunWith(AndroidJUnit4.class)
public class NotificationAssistantsTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private PackageManager mPm;
@Mock
@@ -98,13 +116,43 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
ComponentName mCn = new ComponentName("a", "b");
+
+ // Helper function to hold mApproved lock, avoid GuardedBy lint errors
+ private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) {
+ synchronized (assistant.mApproved) {
+ return assistant.mUserSetServices.get(userId).isEmpty();
+ }
+ }
+
+ private void writeXmlAndReload(int userId) throws Exception {
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mAssistants.writeXml(serializer, false, userId);
+ serializer.endDocument();
+ serializer.flush();
+
+ //fail(baos.toString("UTF-8"));
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+
+ parser.nextTag();
+ mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
+ }
+
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a");
+ com.android.internal.R.string.config_defaultAssistantAccessComponent,
+ mCn.flattenToString());
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -164,33 +212,45 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
true);
- TypedXmlSerializer serializer = Xml.newFastSerializer();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
- serializer.startDocument(null, true);
- mAssistants.writeXml(serializer, true, userId);
- serializer.endDocument();
- serializer.flush();
+ writeXmlAndReload(USER_ALL);
- //fail(baos.toString("UTF-8"));
+ ArrayMap<Boolean, ArraySet<String>> approved =
+ mAssistants.mApproved.get(ActivityManager.getCurrentUser());
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
- parser.nextTag();
- mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ @Test
+ public void testWriteXml_userTurnedOffNAS_backup() throws Exception {
+ int userId = 10;
- ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+ mAssistants.loadDefaultsFromConfig(true);
+
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ mAssistants.setUserSet(userId, true);
+ mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
+ true);
+ assertTrue(mAssistants.mIsUserChanged.get(userId));
+ assertThat(mAssistants.getApproved(userId, true)).isEmpty();
+
+ writeXmlAndReload(userId);
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(userId);
// approved should not be null
assertNotNull(approved);
assertEquals(new ArraySet<>(), approved.get(true));
// user set is maintained
- assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ assertTrue(mAssistants.mIsUserChanged.get(userId));
+ assertThat(mAssistants.getApproved(userId, true)).isEmpty();
}
@Test
@@ -203,11 +263,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
@@ -226,11 +284,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, true,
+ mAssistants.readXml(parser, mNm::canUseManagedServices, true,
ActivityManager.getCurrentUser());
ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
@@ -253,11 +309,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(1)).upgradeUserSet();
assertTrue(mAssistants.mIsUserChanged.get(0));
@@ -273,11 +327,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(0)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -294,11 +346,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(0)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -314,11 +364,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(1)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -334,11 +382,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(1)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -361,7 +407,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
new ByteArrayInputStream(xml.toString().getBytes())), null);
parser.nextTag();
- mAssistants.readXml(parser, null, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, null, false, USER_ALL);
assertEquals(1, mAssistants.getAllowedComponents(0).size());
assertEquals(new ArrayList(Arrays.asList(new ComponentName("a", "a"))),
@@ -378,7 +424,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
parser.nextTag();
- mAssistants.readXml(parser, null, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, null, false, USER_ALL);
verify(mNm, never()).setDefaultAssistantForUser(anyInt());
verify(mAssistants, times(1)).addApprovedList(
@@ -529,10 +575,231 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
assertEquals(new ArraySet<>(), mAssistants.getDefaultComponents());
}
- // Helper function to hold mApproved lock, avoid GuardedBy lint errors
- private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) {
- synchronized (assistant.mApproved) {
- return assistant.mUserSetServices.get(userId).isEmpty();
- }
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAdjustmentTypeSupportedState() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(0);
+
+ ManagedServices.ManagedServiceInfo info =
+ mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
+ mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_NOT_CONVERSATION, false);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId)).contains(
+ Adjustment.KEY_NOT_CONVERSATION);
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(1);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+
+ ManagedServices.ManagedServiceInfo info =
+ mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
+ mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_NOT_CONVERSATION, false);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId)).contains(
+ Adjustment.KEY_NOT_CONVERSATION);
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(1);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testDisallowAdjustmentType() {
+ mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments())
+ .doesNotContain(Adjustment.KEY_RANKING_SCORE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(Adjustment.KEY_TYPE);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testAllowAdjustmentType() {
+ mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments())
+ .doesNotContain(Adjustment.KEY_RANKING_SCORE);
+ mAssistants.allowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments())
+ .contains(Adjustment.KEY_RANKING_SCORE);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testDisallowAdjustmentType_readWriteXml_entries() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.disallowAdjustmentType(KEY_IMPORTANCE);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(
+ Adjustment.KEY_NOT_CONVERSATION);
+ assertThat(mAssistants.getAllowedAssistantAdjustments()).doesNotContain(
+ KEY_IMPORTANCE);
+ }
+
+ @Test
+ public void testDefaultAllowedAdjustments_readWriteXml_entries() throws Exception {
+ mAssistants.loadDefaultsFromConfig(true);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getAllowedAssistantAdjustments())
+ .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENTS);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAssistantAdjustmentKeyTypeState_allow() {
+ assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+ .containsExactly(TYPE_PROMOTION);
+
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
+
+ assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+ .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION));
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAssistantAdjustmentKeyTypeState_disallow() {
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
+ assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testDisallowAdjustmentKeyType_readWriteXml() throws Exception {
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+ .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION));
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testDefaultAllowedKeyAdjustments_readWriteXml() throws Exception {
+ mAssistants.loadDefaultsFromConfig(true);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+ .containsExactly(TYPE_PROMOTION);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsAndDenies() {
+ // Given that a package is allowed to have its type adjusted,
+ String allowedPackage = "allowed.package";
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true);
+
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
+
+ // Set type adjustment disallowed for this package
+ mAssistants.setTypeAdjustmentForPackageState(allowedPackage, false);
+
+ // Then the package is marked as denied
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactly(allowedPackage);
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
+
+ // Set type adjustment allowed again
+ mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true);
+
+ // Then the package is marked as allowed again
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public void testSetAssistantAdjustmentKeyTypeStateForPackage_deniesMultiple() {
+ // Given packages not allowed to have their type adjusted,
+ String deniedPkg1 = "denied.Pkg1";
+ String deniedPkg2 = "denied.Pkg2";
+ String deniedPkg3 = "denied.Pkg3";
+ // Set type adjustment disallowed for these packages
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, false);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false);
+
+ // Then the packages are marked as denied
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg2, deniedPkg3));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3));
+
+ // And when we re-allow one of them,
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, true);
+
+ // Then the rest of the original packages are still marked as denied.
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1));
+ assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public void testSetAssistantAdjustmentKeyTypeStateForPackage_readWriteXml() throws Exception {
+ mAssistants.loadDefaultsFromConfig(true);
+ String deniedPkg1 = "denied.Pkg1";
+ String allowedPkg2 = "allowed.Pkg2";
+ String deniedPkg3 = "denied.Pkg3";
+ // Set type adjustment disallowed or allowed for these packages
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false);
+ mAssistants.setTypeAdjustmentForPackageState(allowedPkg2, true);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
}
-}
+} \ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 69846d52ba74..bc01fc4f29c0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -31,6 +31,12 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_COOLDOWN;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_FLAG_SILENT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_GROUP_ALERT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_NOT_MUTED;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_OTHER_INSISTENT_PLAYING;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -106,6 +112,7 @@ import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Notificat
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.IntPair;
import com.android.server.UiServiceTestCase;
import com.android.server.lights.LightsManager;
@@ -1277,7 +1284,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(r.isInterruptive());
assertEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_FLAG_SILENT);
}
@Test
@@ -1296,7 +1304,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(r.isInterruptive());
assertEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_GROUP_ALERT);
}
@Test
@@ -1862,7 +1871,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyBeepLooped();
NotificationRecord interrupter = getBeepyOtherNotification();
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+ assertThat(
+ mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
verifyBeep(1);
@@ -1880,16 +1891,16 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyDelayedVibrateLooped();
Mockito.reset(mVibrator);
Mockito.reset(mRingtonePlayer);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
// beep wasn't reset
@@ -1908,8 +1919,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyDelayedVibrateLooped();
@@ -1931,8 +1942,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyNeverVibrate();
@@ -1952,14 +1963,15 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyVibrateLooped();
NotificationRecord interrupter = getBuzzyOtherNotification();
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(interrupter,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
verifyVibrate(1);
@@ -2261,10 +2273,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS, true))
+ .isEqualTo(MUTE_REASON_COOLDOWN);
- verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -2277,10 +2292,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2306,8 +2318,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2323,10 +2336,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2364,10 +2374,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2382,9 +2389,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
// update should beep at 0% volume
- mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
assertEquals(-1, r2.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2393,8 +2401,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2411,10 +2420,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
r.getNotification().category = Notification.CATEGORY_EVENT;
@@ -2487,15 +2493,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
// Regular notification: should beep at 0% volume
NotificationRecord r = getBeepyNotification();
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
assertEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
@@ -2526,8 +2530,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Set important conversation
mChannel.setImportantConversation(true);
@@ -2555,10 +2560,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
NotificationRecord r = getBeepyNotification();
@@ -2583,10 +2585,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
- // Trigger avalanche trigger intent
- final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ triggerAvalancheEvent();
// CATEGORY_ALARM is exempted
NotificationRecord r = getBeepyNotification();
@@ -2618,6 +2617,70 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBeepVolume_politeNotif_AvalancheStrategy_exempt_msgCategory() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ triggerAvalancheEvent();
+
+ // Create a conversation group with GROUP_ALERT_SUMMARY behavior
+ // Where the summary is not MessagingStyle
+ final String groupKey = "grup_name";
+ final String shortcutId = "shortcut";
+ NotificationRecord summary = getBeepyNotificationRecord(groupKey, GROUP_ALERT_SUMMARY);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ summary.getNotification().category = Notification.CATEGORY_MESSAGE;
+ ShortcutInfo.Builder sb = new ShortcutInfo.Builder(getContext());
+ summary.setShortcutInfo(sb.setId(shortcutId).build());
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // Post child notifications with GROUP_ALERT_SUMMARY
+ NotificationRecord child = getConversationNotificationRecord(mId, false /* insistent */,
+ false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+ true, false, groupKey, Notification.GROUP_ALERT_SUMMARY, false, mUser, mPkg,
+ shortcutId);
+
+ // Should not beep
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // 2nd update for summary should beep at 50% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // 3rd update for summary should not beep
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ }
+
+ private void triggerAvalancheEvent() throws Exception {
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Wait after avalanche trigger before posting notifications
+ // so that notification#getWhen() is not the same value
+ Thread.sleep(100);
+ }
+
+ @Test
public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
@@ -2752,9 +2815,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
Mockito.reset(mRingtonePlayer);
// next update at 0% volume
- mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
assertEquals(-1, summary.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
}
@@ -2824,9 +2888,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
assertEquals(-1, r2.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2892,6 +2957,94 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBeepVolume_politeNotif_groupAlertSummary() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // child should beep at 0% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 0% volume
+ child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // summary 100% volume (GROUP_ALERT_SUMMARY)
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // next update at 50% volume because only summary was tracked as alerting
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_groupAlertChildren() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // summary 0% volume (GROUP_ALERT_CHILDREN)
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 100% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 50% volume
+ child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 0% volume
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertTrue(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testVibrationIntensity_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2915,8 +3068,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
Mockito.reset(vibratorHelper);
// 2nd update should buzz at 0% intensity
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverVibrate();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
}
@Test
@@ -3008,10 +3162,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
- verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r.getLastAudiblyAlertedMs());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 983e694a8f1a..bf3333349b82 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -835,7 +835,7 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- public void testListenerPost_UpdateLifetimeExtended() throws Exception {
+ public void testListenerPostLifetimeExtended_UpdatesOnlySysui() throws Exception {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY.
@@ -856,44 +856,277 @@ public class NotificationListenersTest extends UiServiceTestCase {
Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
.setContentTitle("new title")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false);
+ .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true);
StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
nb2.build(), userHandle, null, 0);
NotificationRecord toPost = new NotificationRecord(mContext, sbn2, channel);
// Create system ui-like service.
- ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo(
+ ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo(
null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
- info.isSystemUi = true;
- INotificationListener l1 = mock(INotificationListener.class);
- info.service = l1;
- List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info);
+ sysuiInfo.isSystemUi = true;
+ INotificationListener sysuiListener = mock(INotificationListener.class);
+ sysuiInfo.service = sysuiListener;
+
+ // Create two non-system ui-like services.
+ ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo1.isSystemUi = false;
+ INotificationListener otherListener1 = mock(INotificationListener.class);
+ otherInfo1.service = otherListener1;
+
+ ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo2.isSystemUi = false;
+ INotificationListener otherListener2 = mock(INotificationListener.class);
+ otherInfo2.service = otherListener2;
+
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo,
+ otherInfo2);
when(mListeners.getServices()).thenReturn(services);
FieldSetter.setField(mNm,
NotificationManagerService.class.getDeclaredField("mHandler"),
mock(NotificationManagerService.WorkerHandler.class));
doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
- doReturn(mock(NotificationRankingUpdate.class)).when(mNm).makeRankingUpdateLocked(info);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(sysuiInfo);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo1);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo2);
doReturn(false).when(mNm).isInLockDownMode(anyInt());
doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());
- // The notification change is posted to the service listener.
+ // Post notification change to the service listeners.
mListeners.notifyPostedLocked(toPost, old);
// Verify that the post occcurs with the updated notification value.
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mNm.mHandler, times(1)).post(runnableCaptor.capture());
runnableCaptor.getValue().run();
- ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
- ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
- verify(l1, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
- StatusBarNotification sbnResult = sbnCaptor.getValue().get();
+ StatusBarNotification sbnResult = null;
+ if (android.app.Flags.noSbnholder()) {
+ ArgumentCaptor<StatusBarNotification> sbnCaptor =
+ ArgumentCaptor.forClass(StatusBarNotification.class);
+ verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any());
+ sbnResult = sbnCaptor.getValue();
+ } else {
+ ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
+ ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ sbnResult = sbnCaptor.getValue().get();
+ }
+ assertThat(sbnResult.getNotification()
+ .extras.getCharSequence(Notification.EXTRA_TITLE).toString())
+ .isEqualTo("new title");
+
+ verify(otherListener1, never()).onNotificationPosted(any(), any());
+ verify(otherListener2, never()).onNotificationPosted(any(), any());
+ }
+
+ @Test
+ public void testListenerPostLifetimeExtension_postsToAppropriateListeners() throws Exception {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
+
+ // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY.
+ String pkg = "pkg";
+ int uid = 9;
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true);
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb.build(), userHandle, null, 0);
+ NotificationRecord leRecord = new NotificationRecord(mContext, sbn, channel);
+
+ // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY)
+ Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("new title")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false);
+ StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb2.build(), userHandle, null, 0);
+ NotificationRecord nonLeRecord = new NotificationRecord(mContext, sbn2, channel);
+
+ // Create system ui-like service.
+ ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
+ sysuiInfo.isSystemUi = true;
+ INotificationListener sysuiListener = mock(INotificationListener.class);
+ sysuiInfo.service = sysuiListener;
+
+ // Create two non-system ui-like services.
+ ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo1.isSystemUi = false;
+ INotificationListener otherListener1 = mock(INotificationListener.class);
+ otherInfo1.service = otherListener1;
+
+ ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo2.isSystemUi = false;
+ INotificationListener otherListener2 = mock(INotificationListener.class);
+ otherInfo2.service = otherListener2;
+
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo,
+ otherInfo2);
+ when(mListeners.getServices()).thenReturn(services);
+
+ FieldSetter.setField(mNm,
+ NotificationManagerService.class.getDeclaredField("mHandler"),
+ mock(NotificationManagerService.WorkerHandler.class));
+ doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(sysuiInfo);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo1);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo2);
+ doReturn(false).when(mNm).isInLockDownMode(anyInt());
+ doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());
+
+ // The notification change is posted to the service listener.
+ // NonLE to LE should never happen, as LE can't be set in an update by the app.
+ // So we just want to test LE to NonLE.
+ mListeners.notifyPostedLocked(nonLeRecord /*=toPost*/, leRecord /*=old*/);
+
+ // Verify that the post occcurs with the updated notification value.
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mNm.mHandler, times(3)).post(runnableCaptor.capture());
+ List<Runnable> capturedRunnable = runnableCaptor.getAllValues();
+ for (Runnable r : capturedRunnable) {
+ r.run();
+ }
+
+ StatusBarNotification sbnResult = null;
+ if (android.app.Flags.noSbnholder()) {
+ ArgumentCaptor<StatusBarNotification> sbnCaptor =
+ ArgumentCaptor.forClass(StatusBarNotification.class);
+ verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any());
+ sbnResult = sbnCaptor.getValue();
+ } else {
+ ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
+ ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ sbnResult = sbnCaptor.getValue().get();
+ }
assertThat(sbnResult.getNotification()
.extras.getCharSequence(Notification.EXTRA_TITLE).toString())
.isEqualTo("new title");
+
+ if (android.app.Flags.noSbnholder()) {
+ verify(otherListener1, times(1)).onNotificationPostedFull(any(), any());
+ verify(otherListener2, times(1)).onNotificationPostedFull(any(), any());
+ } else {
+ verify(otherListener1, times(1)).onNotificationPosted(any(), any());
+ verify(otherListener2, times(1)).onNotificationPosted(any(), any());
+ }
+ }
+
+ @Test
+ public void testNotifyPostedLocked_postsToAppropriateListeners() throws Exception {
+ // Create original notification
+ String pkg = "pkg";
+ int uid = 9;
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb.build(), userHandle, null, 0);
+ NotificationRecord oldRecord = new NotificationRecord(mContext, sbn, channel);
+
+ // Creates updated notification
+ Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("new title")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb2.build(), userHandle, null, 0);
+ NotificationRecord newRecord = new NotificationRecord(mContext, sbn2, channel);
+
+ // Create system ui-like service.
+ ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
+ sysuiInfo.isSystemUi = true;
+ INotificationListener sysuiListener = mock(INotificationListener.class);
+ sysuiInfo.service = sysuiListener;
+
+ // Create two non-system ui-like services.
+ ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo1.isSystemUi = false;
+ INotificationListener otherListener1 = mock(INotificationListener.class);
+ otherInfo1.service = otherListener1;
+
+ ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo2.isSystemUi = false;
+ INotificationListener otherListener2 = mock(INotificationListener.class);
+ otherInfo2.service = otherListener2;
+
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo,
+ otherInfo2);
+ when(mListeners.getServices()).thenReturn(services);
+
+ FieldSetter.setField(mNm,
+ NotificationManagerService.class.getDeclaredField("mHandler"),
+ mock(NotificationManagerService.WorkerHandler.class));
+ doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(sysuiInfo);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo1);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo2);
+ doReturn(false).when(mNm).isInLockDownMode(anyInt());
+ doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());
+
+ // The notification change is posted to the service listeners.
+ mListeners.notifyPostedLocked(newRecord, oldRecord);
+
+ // Verify that the post occcurs with the updated notification value.
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mNm.mHandler, times(3)).post(runnableCaptor.capture());
+ List<Runnable> capturedRunnable = runnableCaptor.getAllValues();
+ for (Runnable r : capturedRunnable) {
+ r.run();
+ }
+
+ StatusBarNotification sbnResult = null;
+ if (android.app.Flags.noSbnholder()) {
+ ArgumentCaptor<StatusBarNotification> sbnCaptor =
+ ArgumentCaptor.forClass(StatusBarNotification.class);
+ verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any());
+ sbnResult = sbnCaptor.getValue();
+ } else {
+ ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
+ ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ sbnResult = sbnCaptor.getValue().get();
+ }
+ assertThat(sbnResult.getNotification()
+ .extras.getCharSequence(Notification.EXTRA_TITLE).toString())
+ .isEqualTo("new title");
+
+ if (android.app.Flags.noSbnholder()) {
+ verify(otherListener1, times(1)).onNotificationPostedFull(any(), any());
+ verify(otherListener2, times(1)).onNotificationPostedFull(any(), any());
+ } else {
+ verify(otherListener1, times(1)).onNotificationPosted(any(), any());
+ verify(otherListener2, times(1)).onNotificationPosted(any(), any());
+ }
}
/**
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 130690d80b70..074cbb57d5b7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -33,6 +33,7 @@ import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
@@ -42,13 +43,20 @@ import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.VISIBILITY_PRIVATE;
-import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
+import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -73,6 +81,7 @@ import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -88,6 +97,7 @@ import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
@@ -97,12 +107,17 @@ import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Condition.SOURCE_CONTEXT;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
@@ -193,12 +208,15 @@ import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
+import android.app.ZenBypassingApp;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.job.JobScheduler;
import android.app.role.RoleManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -218,7 +236,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.ModuleInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -253,6 +270,7 @@ import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.LimitDevicesRule;
@@ -355,6 +373,7 @@ import java.io.FileOutputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -472,6 +491,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN);
+ private final NotificationChannel mParentChannel =
+ new NotificationChannel(PARENT_CHANNEL_ID, "parentName", IMPORTANCE_DEFAULT);
+ private final NotificationChannel mConversationChannel =
+ new NotificationChannel(
+ CONVERSATION_CHANNEL_ID, "conversationName", IMPORTANCE_DEFAULT);
+
+ private static final String PARENT_CHANNEL_ID = "parentChannelId";
+ private static final String CONVERSATION_CHANNEL_ID = "conversationChannelId";
+ private static final String CONVERSATION_ID = "conversationId";
+
private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
@@ -655,7 +684,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt()))
.thenReturn(INVALID_TASK_ID);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
- when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId });
+ when(mUm.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mUmInternal.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId});
when(mAmi.getCurrentUserId()).thenReturn(mUserId);
when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
@@ -2660,6 +2690,41 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testAggregateGroups_RemoveAppSummary_onClassification() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final int summaryId = 0;
+ final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 1, originalGroupName, false);
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 2, originalGroupName, false);
+ mService.addNotification(r2);
+ final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+ summaryId, originalGroupName, true);
+ mService.addNotification(summary);
+ final String originalGroupKey = summary.getGroupKey();
+ assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+ // Regroup first child notification
+ r1.setOverrideGroupKey("newGroup");
+ // Check that removeAppProvidedSummaryOnClassificationLocked is null
+ // => there is still one child left in the original group
+ assertThat(mService.removeAppProvidedSummaryOnClassificationLocked(r1.getKey(),
+ originalGroupKey)).isNull();
+
+ // Regroup last child notification
+ r2.setOverrideGroupKey("newGroup");
+ // Check that removeAppProvidedSummaryOnClassificationLocked returns the original summary
+ // and that the original app-provided summary is canceled
+ assertThat(mService.removeAppProvidedSummaryOnClassificationLocked(r2.getKey(),
+ originalGroupKey)).isEqualTo(summary);
+ waitForIdle();
+ verify(mWorkerHandler, times(1)).scheduleCancelNotification(any(), eq(summaryId));
+ assertThat(mService.mSummaryByGroupKey).doesNotContainKey(originalGroupKey);
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testUngroupingAggregateSummary() throws Exception {
final String originalGroupName = "originalGroup";
@@ -2870,6 +2935,255 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testRemoveScheduledForceGroup_onNotificationCanceled() throws Exception {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "tag", null,
+ false);
+ when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false);
+ mService.addEnqueuedNotification(r);
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ // Post an update to the notification
+ NotificationRecord r_update =
+ generateNotificationRecord(mTestNotificationChannel, 0, "tag", null, false);
+ mService.addEnqueuedNotification(r_update);
+ runnable = mService.new PostNotificationRunnable(r_update.getKey(),
+ r_update.getSbn().getPackageName(), r_update.getUid(),
+ mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ // Cancel the notification
+ mBinderService.cancelNotificationWithTag(r.getSbn().getPackageName(),
+ r.getSbn().getPackageName(), r.getSbn().getTag(),
+ r.getSbn().getId(), r.getSbn().getUserId());
+ waitForIdle();
+
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ // Check that onNotificationPostedWithDelay was canceled
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ verify(mGroupHelper, never()).onNotificationPostedWithDelay(any(), any(), any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testEnqueueNotification_forceGrouped_clearsSummaryFlag() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ // Old record was a summary and it was auto-grouped
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, true);
+ mService.addNotification(r);
+ mService.convertSummaryToNotificationLocked(r.getKey());
+ mService.addAutogroupKeyLocked(r.getKey(), aggregateGroupName, true);
+
+ assertThat(mService.mNotificationList).hasSize(1);
+
+ // Update record is a summary
+ final Notification updatedNotification = generateNotificationRecord(
+ mTestNotificationChannel, 0, originalGroupName, true).getNotification();
+ assertThat(updatedNotification.flags & FLAG_GROUP_SUMMARY).isEqualTo(FLAG_GROUP_SUMMARY);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), updatedNotification, r.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that FLAG_GROUP_SUMMARY was removed
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testEnqueueNotification_forceGroupedRegular_updatedAsSummary_clearsSummaryFlag()
+ throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ // Old record was not summary and it was auto-grouped
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+ mService.addNotification(r);
+ mService.addAutogroupKeyLocked(r.getKey(), aggregateGroupName, true);
+ assertThat(mService.mNotificationList).hasSize(1);
+
+ // Update record is a summary
+ final Notification updatedNotification = generateNotificationRecord(
+ mTestNotificationChannel, 0, originalGroupName, true).getNotification();
+ assertThat(updatedNotification.flags & FLAG_GROUP_SUMMARY).isEqualTo(FLAG_GROUP_SUMMARY);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), updatedNotification, r.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that FLAG_GROUP_SUMMARY was removed
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testEnqueueNotification_notForceGrouped_dontClearSummaryFlag()
+ throws Exception {
+ final String originalGroupName = "originalGroup";
+
+ // Old record was a summary and it was not auto-grouped
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, true);
+ mService.addNotification(r);
+ assertThat(mService.mNotificationList).hasSize(1);
+
+ // Update record is a summary
+ final Notification updatedNotification = generateNotificationRecord(
+ mTestNotificationChannel, 0, originalGroupName, true).getNotification();
+ assertThat(updatedNotification.flags & FLAG_GROUP_SUMMARY).isEqualTo(FLAG_GROUP_SUMMARY);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), updatedNotification, r.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that FLAG_GROUP_SUMMARY was not removed
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(
+ FLAG_GROUP_SUMMARY);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testRemoveFGSFlagFromNotification_enqueued_forceGrouped_clearsSummaryFlag() {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null,
+ originalGroupName, true);
+ r.getSbn().getNotification().flags &= ~FLAG_GROUP_SUMMARY;
+ r.setOverrideGroupKey(aggregateGroupName);
+ mService.addEnqueuedNotification(r);
+
+ mInternalService.removeForegroundServiceFlagFromNotification(
+ mPkg, r.getSbn().getId(), r.getSbn().getUserId());
+ waitForIdle();
+
+ assertThat(mService.mEnqueuedNotifications).hasSize(1);
+ assertThat(mService.mEnqueuedNotifications.get(0).getFlags() & FLAG_GROUP_SUMMARY)
+ .isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testRemoveFGSFlagFromNotification_posted_forceGrouped_clearsSummaryFlag() {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null,
+ originalGroupName, true);
+ r.getSbn().getNotification().flags &= ~FLAG_GROUP_SUMMARY;
+ r.setOverrideGroupKey(aggregateGroupName);
+ mService.addNotification(r);
+
+ mInternalService.removeForegroundServiceFlagFromNotification(
+ mPkg, r.getSbn().getId(), r.getSbn().getUserId());
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testScheduleGroupHelperWithDelay_onChildNotificationCanceled() throws Exception {
+ // Post summary + 2 child notification
+ final String originalGroupName = "originalGroup";
+ final int summaryId = 0;
+ final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 1, originalGroupName, false);
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 2, originalGroupName, false);
+ mService.addNotification(r2);
+ final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+ summaryId, originalGroupName, true);
+ mService.addNotification(summary);
+ final String originalGroupKey = summary.getGroupKey();
+ assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+ // Cancel the child notifications
+ mBinderService.cancelNotificationWithTag(r1.getSbn().getPackageName(),
+ r1.getSbn().getPackageName(), r1.getSbn().getTag(),
+ r1.getSbn().getId(), r1.getSbn().getUserId());
+ waitForIdle();
+
+ mBinderService.cancelNotificationWithTag(r2.getSbn().getPackageName(),
+ r2.getSbn().getPackageName(), r2.getSbn().getTag(),
+ r2.getSbn().getId(), r2.getSbn().getUserId());
+ waitForIdle();
+
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ // Check that onGroupedNotificationRemovedWithDelay was called only once
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
+ verify(mGroupHelper, times(1)).onGroupedNotificationRemovedWithDelay(eq(summary), any(),
+ any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testCleanupScheduleGroupHelperWithDelay_onAllNotificationCanceled()
+ throws Exception {
+ // Post summary + 2 child notification
+ final String originalGroupName = "originalGroup";
+ final int summaryId = 0;
+ final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 1, originalGroupName, false);
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 2, originalGroupName, false);
+ mService.addNotification(r2);
+ final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+ summaryId, originalGroupName, true);
+ mService.addNotification(summary);
+ final String originalGroupKey = summary.getGroupKey();
+ assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+ // Cancel all notifications: children + summary
+ mBinderService.cancelNotificationWithTag(r1.getSbn().getPackageName(),
+ r1.getSbn().getPackageName(), r1.getSbn().getTag(),
+ r1.getSbn().getId(), r1.getSbn().getUserId());
+ waitForIdle();
+
+ mBinderService.cancelNotificationWithTag(r2.getSbn().getPackageName(),
+ r2.getSbn().getPackageName(), r2.getSbn().getTag(),
+ r2.getSbn().getId(), r2.getSbn().getUserId());
+ waitForIdle();
+
+ mBinderService.cancelNotificationWithTag(summary.getSbn().getPackageName(),
+ summary.getSbn().getPackageName(), summary.getSbn().getTag(),
+ summary.getSbn().getId(), summary.getSbn().getUserId());
+ waitForIdle();
+
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ // Check that onGroupedNotificationRemovedWithDelay was never called: summary was canceled
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any());
+ verify(mGroupHelper, never()).onGroupedNotificationRemovedWithDelay(any(), any(), any());
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
when(mAmi.applyForegroundServiceNotification(
any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
@@ -3078,7 +3392,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Send two cancelations.
mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
sbn.getUserId());
- waitForIdle();
mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
sbn.getUserId());
waitForIdle();
@@ -4075,8 +4388,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testTvExtenderChannelOverride_onTv", 0,
- generateNotificationRecord(null, tv).getNotification(), mUserId);
+ mBinderService.enqueueNotificationWithTag(
+ mPkg,
+ mPkg,
+ "testTvExtenderChannelOverride_onTv",
+ 0,
+ generateNotificationRecord(null, tv).getNotification(),
+ mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@@ -4090,8 +4408,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testTvExtenderChannelOverride_notOnTv",
- 0, generateNotificationRecord(null, tv).getNotification(), mUserId);
+ mBinderService.enqueueNotificationWithTag(
+ mPkg,
+ mPkg,
+ "testTvExtenderChannelOverride_notOnTv",
+ 0,
+ generateNotificationRecord(null, tv).getNotification(),
+ mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
anyBoolean(), anyBoolean());
@@ -4534,8 +4857,161 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mAmi).hasForegroundServiceNotification(anyString(), anyInt(), anyString());
}
+ private void setUpChannelsForConversationChannelTest() throws RemoteException {
+ when(mPreferencesHelper.getNotificationChannel(
+ eq(mPkg), eq(mUid), eq(PARENT_CHANNEL_ID), eq(false)))
+ .thenReturn(mParentChannel);
+ when(mPreferencesHelper.getConversationNotificationChannel(
+ eq(mPkg), eq(mUid), eq(PARENT_CHANNEL_ID), eq(CONVERSATION_ID), eq(false), eq(false)))
+ .thenReturn(mConversationChannel);
+ when(mPackageManager.getPackageUid(mPkg, 0, mUserId)).thenReturn(mUid);
+ }
+
@Test
- public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
+ @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+ public void createConversationChannelForPkgFromPrivilegedListener_cdm_success() throws Exception {
+ // Set up cdm
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+
+ // Set up parent channel
+ setUpChannelsForConversationChannelTest();
+ final NotificationChannel parentChannelCopy = mParentChannel.copy();
+
+ NotificationChannel createdChannel =
+ mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+ null, mPkg, mUser, PARENT_CHANNEL_ID, CONVERSATION_ID);
+
+ // Verify that a channel is created and a copied channel is returned.
+ verify(mPreferencesHelper, times(1)).createNotificationChannel(
+ eq(mPkg), eq(mUid), any(), anyBoolean(), anyBoolean(),
+ eq(mUid), anyBoolean());
+ assertThat(createdChannel).isNotSameInstanceAs(mConversationChannel);
+ assertThat(createdChannel).isEqualTo(mConversationChannel);
+
+ // Verify that the channel creation is not directly use the parent channel.
+ verify(mPreferencesHelper, never()).createNotificationChannel(
+ anyString(), anyInt(), eq(mParentChannel), anyBoolean(), anyBoolean(),
+ anyInt(), anyBoolean());
+
+ // Verify that the content of parent channel is not changed.
+ assertThat(parentChannelCopy).isEqualTo(mParentChannel);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+ public void createConversationChannelForPkgFromPrivilegedListener_cdm_noAccess() throws Exception {
+ // Set up cdm without access
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(emptyList());
+
+ // Set up parent channel
+ setUpChannelsForConversationChannelTest();
+
+ try {
+ mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+ null, mPkg, mUser, "parentId", "conversationId");
+ fail("listeners that don't have a companion device shouldn't be able to call this");
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ verify(mPreferencesHelper, never()).createNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyBoolean(),
+ anyInt(), anyBoolean());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+ public void createConversationChannelForPkgFromPrivilegedListener_assistant_success() throws Exception {
+ // Set up assistant
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(emptyList());
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+ // Set up parent channel
+ setUpChannelsForConversationChannelTest();
+ final NotificationChannel parentChannelCopy = mParentChannel.copy();
+
+ NotificationChannel createdChannel =
+ mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+ null, mPkg, mUser, PARENT_CHANNEL_ID, CONVERSATION_ID);
+
+ // Verify that a channel is created and a copied channel is returned.
+ verify(mPreferencesHelper, times(1)).createNotificationChannel(
+ eq(mPkg), eq(mUid), any(), anyBoolean(), anyBoolean(),
+ eq(mUid), anyBoolean());
+ assertThat(createdChannel).isNotSameInstanceAs(mConversationChannel);
+ assertThat(createdChannel).isEqualTo(mConversationChannel);
+
+ // Verify that the channel creation is not directly use the parent channel.
+ verify(mPreferencesHelper, never()).createNotificationChannel(
+ anyString(), anyInt(), eq(mParentChannel), anyBoolean(), anyBoolean(),
+ anyInt(), anyBoolean());
+
+ // Verify that the content of parent channel is not changed.
+ assertThat(parentChannelCopy).isEqualTo(mParentChannel);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+ public void createConversationChannelForPkgFromPrivilegedListener_assistant_noAccess() throws Exception {
+ // Set up assistant without access
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(emptyList());
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
+
+ // Set up parent channel
+ setUpChannelsForConversationChannelTest();
+
+ try {
+ mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+ null, mPkg, mUser, "parentId", "conversationId");
+ fail("listeners that don't have a companion device shouldn't be able to call this");
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ verify(mPreferencesHelper, never()).createNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyBoolean(),
+ anyInt(), anyBoolean());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+ public void createConversationChannelForPkgFromPrivilegedListener_badUser() throws Exception {
+ // Set up bad user
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(mPkg, mPkg);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ // Set up parent channel
+ setUpChannelsForConversationChannelTest();
+
+ try {
+ mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+ null, mPkg, mUser, "parentId", "conversationId");
+ fail("listener getting channels from a user they cannot see");
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ verify(mPreferencesHelper, never()).createNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyBoolean(),
+ anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void updateNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
+
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
@@ -4555,7 +5031,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
+ public void updateNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
.thenReturn(emptyList());
@@ -4577,7 +5053,51 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
+ public void updateNotificationChannelFromPrivilegedListener_assistant_success() throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(emptyList());
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ mBinderService.updateNotificationChannelFromPrivilegedListener(
+ null, mPkg, Process.myUserHandle(), mTestNotificationChannel);
+
+ verify(mPreferencesHelper, times(1)).updateNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
+
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+ eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+ }
+
+ @Test
+ public void updateNotificationChannelFromPrivilegedListener_assistant_noAccess() throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(emptyList());
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
+
+ try {
+ mBinderService.updateNotificationChannelFromPrivilegedListener(
+ null, mPkg, Process.myUserHandle(), mTestNotificationChannel);
+ fail("listeners that don't have a companion device shouldn't be able to call this");
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ verify(mPreferencesHelper, never()).updateNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
+
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+ eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+ }
+
+ @Test
+ public void updateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
@@ -4603,7 +5123,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission()
+ public void updateNotificationChannelFromPrivilegedListener_noSoundUriPermission()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
@@ -4635,7 +5155,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound()
+ public void updateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
@@ -4652,7 +5172,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
doThrow(new SecurityException("no access")).when(mUgmInternal)
.checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri),
- anyInt(), eq(Process.myUserHandle().getIdentifier()));
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ mBinderService.updateNotificationChannelFromPrivilegedListener(
+ null, mPkg, Process.myUserHandle(), updatedNotificationChannel);
+
+ verify(mPreferencesHelper, times(1)).updateNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
+
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+ eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+ }
+
+ @Test
+ public void
+ updateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm()
+ throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+ when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ // Missing Uri permissions for the old channel sound
+ final Uri oldSoundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(Process.myUid()), any(), eq(oldSoundUri),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ // Has Uri permissions for the old channel sound
+ final Uri newSoundUri = Uri.parse("content://media/test/sound/uri");
+ final NotificationChannel updatedNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+ updatedNotificationChannel.setSound(newSoundUri,
+ updatedNotificationChannel.getAudioAttributes());
mBinderService.updateNotificationChannelFromPrivilegedListener(
null, mPkg, Process.myUserHandle(), updatedNotificationChannel);
@@ -4675,7 +5230,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
null, mPkg, Process.myUserHandle());
verify(mPreferencesHelper, times(1)).getNotificationChannels(
- anyString(), anyInt(), anyBoolean());
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
@@ -4693,7 +5248,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
verify(mPreferencesHelper, never()).getNotificationChannels(
- anyString(), anyInt(), anyBoolean());
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
@@ -4708,7 +5263,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
null, mPkg, Process.myUserHandle());
verify(mPreferencesHelper, times(1)).getNotificationChannels(
- anyString(), anyInt(), anyBoolean());
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
@@ -4728,7 +5283,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
verify(mPreferencesHelper, never()).getNotificationChannels(
- anyString(), anyInt(), anyBoolean());
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
@@ -4750,7 +5305,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
verify(mPreferencesHelper, never()).getNotificationChannels(
- anyString(), anyInt(), anyBoolean());
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
@@ -6178,7 +6733,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
false,
- UserHandle.USER_ALL);
+ UserHandle.USER_ALL, null);
verify(mListeners, times(1)).readXml(any(), any(), anyBoolean(), anyInt());
verify(mConditionProviders, times(1)).readXml(any(), any(), anyBoolean(), anyInt());
verify(mAssistants, times(1)).readXml(any(), any(), anyBoolean(), anyInt());
@@ -6198,7 +6753,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
false,
- UserHandle.USER_ALL);
+ UserHandle.USER_ALL, null);
verify(mSnoozeHelper, times(1)).readXml(any(TypedXmlPullParser.class), anyLong());
}
@@ -6210,7 +6765,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
false,
- UserHandle.USER_ALL);
+ UserHandle.USER_ALL, null);
verify(mListeners, never()).readXml(any(), any(), anyBoolean(), anyInt());
verify(mConditionProviders, never()).readXml(any(), any(), anyBoolean(), anyInt());
verify(mAssistants, never()).readXml(any(), any(), anyBoolean(), anyInt());
@@ -6242,7 +6797,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
- 10);
+ 10, null);
verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
@@ -6268,7 +6823,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
- 10);
+ 10, null);
verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
@@ -6296,7 +6851,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
- 10);
+ 10, null);
verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
@@ -6323,13 +6878,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
- 10);
+ 10, null);
verify(mListeners, times(1)).readXml(any(), any(), eq(true), eq(10));
verify(mConditionProviders, times(1)).readXml(any(), any(), eq(true), eq(10));
verify(mAssistants, times(1)).readXml(any(), any(), eq(true), eq(10));
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING)
+ public void testReadPolicyXml_backupRestoreLogging() throws Exception {
+ BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class);
+
+ if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
+ // By default, the ZenModeHelper only has a configuration for the system user.
+ // If the current user is not the system user, the user must be updated.
+ mService.mZenModeHelper.onUserSwitched(ActivityManager.getCurrentUser());
+ }
+ UserInfo ui = new UserInfo(ActivityManager.getCurrentUser(), "Clone", UserInfo.FLAG_FULL);
+ ui.userType = USER_TYPE_FULL_SYSTEM;
+ when(mUmInternal.getUserInfo(ActivityManager.getCurrentUser())).thenReturn(ui);
+ when(mPermissionHelper.getNotificationPermissionValues(ActivityManager.getCurrentUser()))
+ .thenReturn(new ArrayMap<>());
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mService.writePolicyXml(baos, true, ActivityManager.getCurrentUser(), logger);
+ serializer.flush();
+
+ mService.readPolicyXml(
+ new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+ true, ActivityManager.getCurrentUser(), logger);
+
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, 1);
+ verify(logger, never())
+ .logItemsBackupFailed(eq(DATA_TYPE_ZEN_CONFIG), anyInt(), anyString());
+
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_CONFIG, 1);
+ verify(logger, never())
+ .logItemsRestoreFailed(eq(DATA_TYPE_ZEN_CONFIG), anyInt(), anyString());
+ }
+
+ @Test
public void testLocaleChangedCallsUpdateDefaultZenModeRules() throws Exception {
ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = mZenModeHelper;
@@ -7032,6 +7622,65 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testClassificationChannelAdjustmentsLogged() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r1.getLifespanMs(anyLong())).thenReturn(234);
+
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ // Enqueues the notification to be posted, so hasPosted will be false.
+ mService.addEnqueuedNotification(r1);
+
+ // Test an adjustment for an enqueued notification
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals, "",
+ r1.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
+ assertTrue(mService.checkLastClassificationChannelLog(false /*hasPosted*/,
+ true /*isAlerting*/, 3 /*TYPE_NEWS*/, 234));
+
+ // Set up notifications that will be adjusted
+ // This notification starts on a low importance channel, so isAlerting is false.
+ NotificationChannel mLowImportanceNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_LOW);
+ final NotificationRecord r2 = spy(generateNotificationRecord(
+ mLowImportanceNotificationChannel, 1, null, true));
+ when(r2.getLifespanMs(anyLong())).thenReturn(345);
+
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ // Adds the notification as already posted, so hasPosted will be true.
+ mService.addNotification(r2);
+ // The signal is removed when used so it has to be readded.
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
+ assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/,
+ false /*isAlerting*/, 3 /*TYPE_NEWS*/, 345)); // currently failing
+
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_PROMOTION);
+ Adjustment adjustment3 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
+ assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/,
+ false /*isAlerting*/, 1 /*TYPE_PROMOTION*/, 345));
+ }
+
+ @Test
public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
@@ -7205,9 +7854,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.MessagingStyle("").addMessage(message2));
- NotificationRecord recordB = new NotificationRecord(mContext, new StatusBarNotification(mPkg,
- mPkg, 0, "tag", mUid, 0, nbB.build(), UserHandle.getUserHandleForUid(mUid), null, 0),
- c);
+ NotificationRecord recordB =
+ new NotificationRecord(
+ mContext,
+ new StatusBarNotification(
+ mPkg,
+ mPkg,
+ 0,
+ "tag",
+ mUid,
+ 0,
+ nbB.build(),
+ UserHandle.getUserHandleForUid(mUid),
+ null,
+ 0),
+ c);
// Update means we drop access to first
reset(mUgmInternal);
@@ -7493,7 +8154,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF);
@@ -7513,7 +8174,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_NOTIFICATION_LIST);
@@ -7530,7 +8191,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR);
@@ -7548,7 +8209,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF);
@@ -7567,7 +8228,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_NOTIFICATION_LIST | SUPPRESSED_EFFECT_AMBIENT
@@ -7587,7 +8248,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR);
@@ -8281,8 +8942,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testAreBubblesEnabled_false() throws Exception {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_BUBBLES, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_BUBBLES, 0, UserHandle.getUserId(mUid));
mService.mPreferencesHelper.updateBubblesEnabled();
assertFalse(mBinderService.areBubblesEnabled(UserHandle.getUserHandleForUid(mUid)));
}
@@ -10229,7 +10890,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
- verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+ verify(mockZenModeHelper).addAutomaticZenRule(any(), eq("android"), eq(rule),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@@ -10251,7 +10912,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
- verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+ verify(mockZenModeHelper).addAutomaticZenRule(any(), eq("android"), eq(rule),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@@ -10271,9 +10932,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(rule, "another.package", false);
// verify that zen mode helper gets passed in the package name from the arg, not the owner
- verify(mockZenModeHelper).addAutomaticZenRule(
- eq("another.package"), eq(rule), eq(ZenModeConfig.ORIGIN_APP),
- anyString(), anyInt()); // doesn't count as a system/systemui call
+ verify(mockZenModeHelper).addAutomaticZenRule(any(), eq("another.package"), eq(rule),
+ eq(ZenModeConfig.ORIGIN_APP), anyString(),
+ anyInt()); // doesn't count as a system/systemui call
}
@Test
@@ -10290,7 +10951,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(rule, mPkg, /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq(mPkg), eq(rule), anyInt(), any(), anyInt());
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq(mPkg), eq(rule), anyInt(), any(),
+ anyInt());
}
@Test
@@ -10325,7 +10987,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(rule, mPkg, /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq(mPkg), eq(rule), anyInt(), any(), anyInt());
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq(mPkg), eq(rule), anyInt(), any(),
+ anyInt());
}
@Test
@@ -10358,7 +11021,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(rule, mPkg, /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq(mPkg), eq(rule), anyInt(), any(), anyInt());
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq(mPkg), eq(rule), anyInt(), any(),
+ anyInt());
}
private void addAutomaticZenRule_restrictedRuleTypeCannotBeUsedByRegularApps(
@@ -10386,7 +11050,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
- verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq("pkg"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@@ -10398,7 +11062,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq("pkg"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@@ -10410,7 +11074,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq("pkg"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_APP), anyString(), anyInt());
}
@@ -10432,7 +11096,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
- verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).updateAutomaticZenRule(any(), eq("id"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@@ -10454,7 +11118,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
- verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
+ verify(zenModeHelper).removeAutomaticZenRule(any(), eq("id"),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@@ -10479,7 +11143,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
SOURCE_USER_ACTION);
mBinderService.setAutomaticZenRuleState("id", withSourceUser);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceUser),
eq(ZenModeConfig.ORIGIN_USER_IN_APP), anyInt());
}
@@ -10494,7 +11158,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
SOURCE_CONTEXT);
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceContext),
eq(ZenModeConfig.ORIGIN_APP), anyInt());
}
@@ -10509,7 +11173,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
SOURCE_USER_ACTION);
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceContext),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyInt());
}
@Test
@@ -10523,10 +11187,35 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
SOURCE_CONTEXT);
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceContext),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyInt());
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_MULTIUSER)
+ public void getAutomaticZenRules_fromSystem_readsWithCurrentUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ // Representative used to verify getCallingZenUser().
+ mBinderService.getAutomaticZenRules();
+
+ verify(zenModeHelper).getAutomaticZenRules(eq(UserHandle.CURRENT));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_MULTIUSER)
+ public void getAutomaticZenRules_fromNormalPackage_readsWithBinderUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ // Representative used to verify getCallingZenUser().
+ mBinderService.getAutomaticZenRules();
+
+ verify(zenModeHelper).getAutomaticZenRules(eq(Binder.getCallingUserHandle()));
+ }
+
/** Prepares for a zen-related test that uses a mocked {@link ZenModeHelper}. */
private ZenModeHelper setUpMockZenTest() {
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
@@ -10589,19 +11278,71 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(102)));
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void onAutomaticRuleStatusChanged_sendsBroadcastToRuleOwner() throws Exception {
+ mService.mZenModeHelper.getCallbacks().forEach(c -> c.onAutomaticRuleStatusChanged(
+ mUserId, "rule.owner.pkg", "rule_id", AUTOMATIC_RULE_STATUS_ACTIVATED));
+
+ Intent expected = new Intent(ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED)
+ .setPackage("rule.owner.pkg")
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, "rule_id")
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS, AUTOMATIC_RULE_STATUS_ACTIVATED)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+ verify(mContext).sendBroadcastAsUser(eqIntent(expected), eq(UserHandle.of(mUserId)));
+ }
+
private static Intent eqIntent(Intent wanted) {
return ArgumentMatchers.argThat(
new ArgumentMatcher<Intent>() {
@Override
public boolean matches(Intent argument) {
return wanted.filterEquals(argument)
- && wanted.getFlags() == argument.getFlags();
+ && wanted.getFlags() == argument.getFlags()
+ && equalBundles(wanted.getExtras(), argument.getExtras());
}
@Override
public String toString() {
return wanted.toString();
}
+
+ private boolean equalBundles(Bundle one, Bundle two) {
+ if (one == null && two == null) {
+ return true;
+ }
+ if ((one == null) != (two == null)) {
+ return false;
+ }
+ if (one.size() != two.size()) {
+ return false;
+ }
+
+ HashSet<String> setOne = new HashSet<>(one.keySet());
+ setOne.addAll(two.keySet());
+
+ for (String key : setOne) {
+ if (!one.containsKey(key) || !two.containsKey(key)) {
+ return false;
+ }
+
+ Object valueOne = one.get(key);
+ Object valueTwo = two.get(key);
+ if (valueOne instanceof Bundle
+ && valueTwo instanceof Bundle
+ && !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
+ return false;
+ } else if (valueOne == null) {
+ if (valueTwo != null) {
+ return false;
+ }
+ } else if (!valueOne.equals(valueTwo)) {
+ return false;
+ }
+ }
+ return true;
+ }
});
}
@@ -12606,6 +13347,38 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void getPackagesBypassingDnd_blocked()
+ throws RemoteException, PackageManager.NameNotFoundException {
+
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id4", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(false);
+ // has DND access, so can set bypassDnd attribute
+ mService.mPreferencesHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+ /*has DND access*/ true, UID_N_MR1, false);
+ mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel2, true, true,
+ UID_P, false);
+ mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
+ UID_P, false);
+
+ when(mPackageManager.getPackageUid(eq(PKG_P), anyLong(), anyInt())).thenReturn(UID_P);
+ when(mPackageManager.getPackageUid(eq(PKG_N_MR1), anyLong(), anyInt()))
+ .thenReturn(UID_N_MR1);
+ when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false);
+ when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true);
+
+ enableInteractAcrossUsers();
+ assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList())
+ .containsExactly(new ZenBypassingApp(PKG_P, false));
+ }
+
+ @Test
public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
mService.setPreferencesHelper(mPreferencesHelper);
@@ -12619,125 +13392,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetPackagesBypassingDnd_empty() throws RemoteException {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
+ List<String> result = mBinderService.getPackagesBypassingDnd(mUserId).getList();
assertThat(result).isEmpty();
}
@Test
- public void testGetPackagesBypassingDnd_excludeConversationChannels() throws RemoteException {
- mService.setPreferencesHelper(mPreferencesHelper);
-
- // Set packages
- PackageInfo pkg0 = new PackageInfo();
- pkg0.packageName = "pkg0";
- pkg0.applicationInfo = new ApplicationInfo();
- pkg0.applicationInfo.uid = mUid;
- PackageInfo pkg1 = new PackageInfo();
- pkg1.packageName = "pkg1";
- pkg1.applicationInfo = new ApplicationInfo();
- pkg1.applicationInfo.uid = mUid;
- PackageInfo pkg2 = new PackageInfo();
- pkg2.packageName = "pkg2";
- pkg2.applicationInfo = new ApplicationInfo();
- pkg2.applicationInfo.uid = mUid;
-
- when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
- .thenReturn(List.of(pkg0, pkg1, pkg2));
-
- // Conversation channels
- NotificationChannel nc0 = new NotificationChannel("id0", "id0",
- NotificationManager.IMPORTANCE_HIGH);
- nc0.setConversationId("parentChannel", "conversationId");
-
- // Demoted conversation channel
- NotificationChannel nc1 = new NotificationChannel("id1", "id1",
- NotificationManager.IMPORTANCE_HIGH);
- nc1.setConversationId("parentChannel", "conversationId");
- nc1.setDemoted(true);
-
- // Non-conversation channels
- NotificationChannel nc2 = new NotificationChannel("id2", "id2",
- NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel nc3 = new NotificationChannel("id3", "id3",
- NotificationManager.IMPORTANCE_HIGH);
-
- ParceledListSlice<NotificationChannel> pls0 =
- new ParceledListSlice(ImmutableList.of(nc0));
- ParceledListSlice<NotificationChannel> pls1 =
- new ParceledListSlice(ImmutableList.of(nc1));
- ParceledListSlice<NotificationChannel> pls2 =
- new ParceledListSlice(ImmutableList.of(nc2, nc3));
-
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
- .thenReturn(pls0);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
- .thenReturn(pls1);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
- .thenReturn(pls2);
-
- List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, false);
-
- assertThat(result).containsExactly("pkg1", "pkg2");
- }
-
- @Test
- public void testGetPackagesBypassingDnd_includeConversationChannels() throws RemoteException {
- mService.setPreferencesHelper(mPreferencesHelper);
-
- // Set packages
- PackageInfo pkg0 = new PackageInfo();
- pkg0.packageName = "pkg0";
- pkg0.applicationInfo = new ApplicationInfo();
- pkg0.applicationInfo.uid = mUid;
- PackageInfo pkg1 = new PackageInfo();
- pkg1.packageName = "pkg1";
- pkg1.applicationInfo = new ApplicationInfo();
- pkg1.applicationInfo.uid = mUid;
- PackageInfo pkg2 = new PackageInfo();
- pkg2.packageName = "pkg2";
- pkg2.applicationInfo = new ApplicationInfo();
- pkg2.applicationInfo.uid = mUid;
-
- when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
- .thenReturn(List.of(pkg0, pkg1, pkg2));
-
- // Conversation channels
- NotificationChannel nc0 = new NotificationChannel("id0", "id0",
- NotificationManager.IMPORTANCE_HIGH);
- nc0.setConversationId("parentChannel", "conversationId");
-
- // Demoted conversation channel
- NotificationChannel nc1 = new NotificationChannel("id1", "id1",
- NotificationManager.IMPORTANCE_HIGH);
- nc1.setConversationId("parentChannel", "conversationId");
- nc1.setDemoted(true);
-
- // Non-conversation channels
- NotificationChannel nc2 = new NotificationChannel("id2", "id2",
- NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel nc3 = new NotificationChannel("id3", "id3",
- NotificationManager.IMPORTANCE_HIGH);
-
- ParceledListSlice<NotificationChannel> pls0 =
- new ParceledListSlice(ImmutableList.of(nc0));
- ParceledListSlice<NotificationChannel> pls1 =
- new ParceledListSlice(ImmutableList.of(nc1));
- ParceledListSlice<NotificationChannel> pls2 =
- new ParceledListSlice(ImmutableList.of(nc2, nc3));
-
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
- .thenReturn(pls0);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
- .thenReturn(pls1);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
- .thenReturn(pls2);
-
- List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
-
- assertThat(result).containsExactly("pkg0", "pkg1", "pkg2");
- }
-
- @Test
public void testMatchesCallFilter_noPermissionShouldThrow() throws Exception {
// set the testable NMS to not system uid/appid
mService.isSystemUid = false;
@@ -13889,9 +14548,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
}
- private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage,
- boolean isImageBitmap, boolean isExpired) {
- Notification.Builder builder = new Notification.Builder(mContext);
+ private Notification createBigPictureNotification(boolean isBigPictureStyle, boolean hasImage,
+ boolean isImageBitmap) {
+ Notification.Builder builder = new Notification.Builder(mContext)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
Notification.BigPictureStyle style = new Notification.BigPictureStyle();
if (isBigPictureStyle && hasImage) {
@@ -13907,12 +14567,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification notification = builder.setChannelId(TEST_CHANNEL_ID).build();
+ return notification;
+ }
+
+ private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage,
+ boolean isImageBitmap, boolean isExpired) {
long timePostedMs = System.currentTimeMillis();
if (isExpired) {
timePostedMs -= BITMAP_DURATION.toMillis();
}
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
- notification, UserHandle.getUserHandleForUid(mUid), null, timePostedMs);
+ createBigPictureNotification(isBigPictureStyle, hasImage, isImageBitmap),
+ UserHandle.getUserHandleForUid(mUid), null, timePostedMs);
return new NotificationRecord(mContext, sbn, mTestNotificationChannel);
}
@@ -13924,6 +14590,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testRemoveBitmaps_canRemoveRevokedDelegate() throws Exception {
+ Notification n = createBigPictureNotification(true, true, true);
+ long timePostedMs = System.currentTimeMillis();
+ timePostedMs -= BITMAP_DURATION.toMillis();
+
+ when(mPermissionHelper.hasPermission(UID_O)).thenReturn(true);
+ when(mPackageManagerInternal.isSameApp(PKG_O, UID_O, UserHandle.getUserId(UID_O)))
+ .thenReturn(true);
+ mService.mPreferencesHelper.createNotificationChannel(PKG_O, UID_O,
+ mTestNotificationChannel, true /* fromTargetApp */, false, UID_O,
+ false);
+ mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG_O, "old.delegate", 8, "tag",
+ UID_O, 0, n, UserHandle.getUserHandleForUid(UID_O), null, timePostedMs);
+
+ mService.addNotification(new NotificationRecord(mContext, sbn, mTestNotificationChannel));
+ mInternalService.removeBitmaps();
+
+ waitForIdle();
+
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.EnqueueNotificationRunnable.class));
+ }
+
+ @Test
public void testRemoveBitmaps_notBigPicture_noRepost() {
addRecordAndRemoveBitmaps(
createBigPictureRecord(
@@ -14880,8 +15573,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
i, null, false).getSbn();
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testCannotPostNonUijWhenOverLimit",
- sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.enqueueNotificationWithTag(
+ mPkg,
+ mPkg,
+ "testCannotPostNonUijWhenOverLimit",
+ sbn.getId(),
+ sbn.getNotification(),
+ sbn.getUserId());
waitForIdle();
}
@@ -15611,6 +16309,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class));
+
+ mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
// No exception!
}
@@ -15646,7 +16346,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(any(), eq("package"), anyInt(),
+ eq(policy));
}
@Test
@@ -15662,7 +16363,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ verify(zenModeHelper).setNotificationPolicy(any(), eq(policy), anyInt(), anyInt());
}
@Test
@@ -15709,9 +16410,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.setNotificationPolicy("package", policy, false);
if (canSetGlobalPolicy) {
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ verify(zenModeHelper).setNotificationPolicy(any(), eq(policy), anyInt(), anyInt());
} else {
- verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
+ verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(any(), anyString(), anyInt(),
eq(policy));
}
}
@@ -15729,7 +16430,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ verify(zenModeHelper).setNotificationPolicy(any(), eq(policy), anyInt(), anyInt());
}
@Test
@@ -15744,7 +16445,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.getNotificationPolicy("package");
- verify(zenHelper).getNotificationPolicyFromImplicitZenRule(eq("package"));
+ verify(zenHelper).getNotificationPolicyFromImplicitZenRule(any(), eq("package"));
}
@Test
@@ -15759,7 +16460,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
- verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(),
+ verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(any(), eq("package"), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
}
@@ -15776,9 +16477,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
- verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), eq("package"),
- anyInt());
+ verify(zenModeHelper).setManualZenMode(any(), eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
+ eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), eq("package"), anyInt());
}
@Test
@@ -15822,10 +16522,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
if (canSetGlobalPolicy) {
- verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.ORIGIN_APP), anyString(), eq("package"), anyInt());
+ verify(zenModeHelper).setManualZenMode(any(), eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
+ eq(null), eq(ZenModeConfig.ORIGIN_APP), anyString(), eq("package"), anyInt());
} else {
- verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
+ verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(any(), anyString(), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
}
}
@@ -15844,8 +16544,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
INTERRUPTION_FILTER_PRIORITY);
- verify(mService.mZenModeHelper).applyGlobalZenModeAsImplicitZenRule(eq("pkg"), eq(mUid),
- eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ verify(mService.mZenModeHelper).applyGlobalZenModeAsImplicitZenRule(any(), eq("pkg"),
+ eq(mUid), eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
}
@Test
@@ -15862,9 +16562,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
INTERRUPTION_FILTER_PRIORITY);
- verify(mService.mZenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
- eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(),
- eq("pkg"), eq(mUid));
+ verify(mService.mZenModeHelper).setManualZenMode(any(),
+ eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM),
+ anyString(), eq("pkg"), eq(mUid));
}
@Test
@@ -15936,6 +16636,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(updatedRule.getValue().isEnabled()).isFalse();
}
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ public void setNotificationPolicy_fromSystemApp_appliesPriorityChannelsAllowed()
+ throws Exception {
+ setUpRealZenTest();
+ // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
+ mService.mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(0, 0, 0, 0, Policy.policyState(true, true), 0),
+ ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
+
+ // The caller will supply states with "wrong" hasPriorityChannels.
+ int stateBlockingPriorityChannels = Policy.policyState(false, false);
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(1, 0, 0, 0, stateBlockingPriorityChannels, 0), false);
+
+ // hasPriorityChannels is untouched and allowPriorityChannels was updated.
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, false));
+
+ // Same but setting allowPriorityChannels to true.
+ int stateAllowingPriorityChannels = Policy.policyState(false, true);
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(2, 0, 0, 0, stateAllowingPriorityChannels, 0), false);
+
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(2);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, true));
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_fromRegularAppThatCanModifyPolicy_ignoresState()
+ throws Exception {
+ setUpRealZenTest();
+ // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
+ mService.mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(0, 0, 0, 0, Policy.policyState(true, true), 0),
+ ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
+ mService.setCallerIsNormalPackage();
+
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(1, 0, 0, 0, Policy.policyState(false, false), 0), false);
+
+ // Policy was updated but the attempt to change state was ignored (it's a @hide API).
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, true));
+ }
+
/** Prepares for a zen-related test that uses the real {@link ZenModeHelper}. */
private void setUpRealZenTest() throws Exception {
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
@@ -16540,6 +17291,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
mService.setHandler(handler);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
Bundle signals = new Bundle();
signals.putInt(KEY_TYPE, TYPE_NEWS);
@@ -16553,19 +17306,72 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
r.applyAdjustments();
assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+
+ signals.putInt(KEY_TYPE, TYPE_PROMOTION);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ assertThat(r.getChannel().getId()).isEqualTo(PROMOTIONS_ID);
+
+ signals.putInt(KEY_TYPE, TYPE_SOCIAL_MEDIA);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ assertThat(r.getChannel().getId()).isEqualTo(SOCIAL_MEDIA_ID);
+
+ signals.putInt(KEY_TYPE, TYPE_CONTENT_RECOMMENDATION);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ assertThat(r.getChannel().getId()).isEqualTo(RECS_ID);
+ }
+
+ @Test
+ @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
+ android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI})
+ public void testApplyAdjustment_keyTypeForDisallowedPackage_DoesNotApply() throws Exception {
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(r);
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+ Bundle signals = new Bundle();
+ signals.putInt(KEY_TYPE, TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(
+ r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+
+ waitForIdle();
+
+ r.applyAdjustments();
+
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+
+ // When we block adjustments for this package
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(false);
+
+ signals.putInt(KEY_TYPE, TYPE_PROMOTION);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ // Then the adjustment is not applied.
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_granted() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -16579,6 +17385,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
@@ -16591,6 +17398,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0,
@@ -16611,7 +17419,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(r);
mService.addEnqueuedNotification(r1);
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
@@ -16632,16 +17440,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -16651,9 +17458,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(r);
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
ArgumentCaptor<NotificationRecord> captor =
@@ -16663,12 +17470,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_revoked() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// start from true state
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
@@ -16676,6 +17481,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -16690,6 +17496,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -16709,7 +17516,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(r);
mService.addEnqueuedNotification(r1);
- mBinderService.setCanBePromoted(mPkg, mUid, false);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
waitForIdle();
@@ -16728,12 +17535,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// start from true state
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
@@ -16741,6 +17546,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -16751,9 +17557,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(r);
- mBinderService.setCanBePromoted(mPkg, mUid, false);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
waitForIdle();
- mBinderService.setCanBePromoted(mPkg, mUid, false);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
waitForIdle();
ArgumentCaptor<NotificationRecord> captor =
@@ -16763,20 +17569,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification() throws Exception {
- mBinderService.setCanBePromoted(mPkg, mUid, true);
- assertThat(mBinderService.canBePromoted(mPkg, mUid)).isTrue();
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
+ assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isTrue();
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
- //assertThat(n.hasPromotableCharacteristics()).isTrue();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -16794,15 +17598,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification_noPermission() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+ assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isFalse();
+
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
@@ -16822,16 +17628,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification_unimportantNotification() throws Exception {
- mBinderService.setCanBePromoted(mPkg, mUid, true);
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
Notification n = new Notification.Builder(mContext, mMinChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
@@ -16849,4 +17654,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
FLAG_PROMOTED_ONGOING)).isFalse();
}
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testAppCannotUseReservedBundleChannels() throws Exception {
+ mService.mPreferencesHelper.createReservedChannel(mPkg, mUid, TYPE_NEWS);
+ NotificationChannel news = mBinderService.getNotificationChannel(
+ mPkg, mContext.getUserId(), mPkg, NEWS_ID);
+ assertThat(news).isNotNull();
+
+ NotificationRecord nr = generateNotificationRecord(news);
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).isEmpty();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 50a5f658f059..4391152220c0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -546,7 +546,8 @@ public class NotificationRecordTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
+ @EnableFlags(com.android.server.notification.Flags
+ .FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI_FOR_CHANNEL)
public void testVibration_customVibrationForSound_withVibrationUri() throws IOException {
defaultChannel.enableVibration(true);
VibrationInfo vibration = getTestingVibration(mVibrator);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 411a6102f45a..361df94e8a90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -44,7 +44,7 @@ import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.server.UiServiceTestCase;
@@ -89,7 +89,7 @@ import java.util.stream.Stream;
import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
-@EnableFlags({Flags.FLAG_VISIT_PERSON_URI, Flags.FLAG_API_RICH_ONGOING})
+@EnableFlags({Flags.FLAG_API_RICH_ONGOING})
public class NotificationVisitUrisTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7d63062784f9..80e86a15156a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -18,6 +18,7 @@ package com.android.server.notification;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.Flags.FLAG_MODES_UI;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.NotificationChannel.ALLOW_BUBBLE_ON;
@@ -45,12 +46,19 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
+import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.content.ContentResolver.SCHEME_FILE;
import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
-
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_OTHER;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
import static android.service.notification.Flags.notificationClassification;
@@ -59,9 +67,11 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID;
@@ -77,12 +87,14 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -96,6 +108,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.ZenBypassingApp;
import android.content.AttributionSource;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -243,7 +256,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
- FLAG_NOTIFICATION_CLASSIFICATION);
+ android.app.Flags.FLAG_API_RICH_ONGOING,
+ FLAG_NOTIFICATION_CLASSIFICATION, FLAG_MODES_UI);
}
public PreferencesHelperTest(FlagsParameterization flags) {
@@ -347,7 +361,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT);
@@ -368,10 +382,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -481,7 +495,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
private void resetZenModeHelper() {
reset(mMockZenModeHelper);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
}
private void setUpPackageWithUid(String packageName, int uid) throws Exception {
@@ -633,6 +647,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel updateNews = null;
if (notificationClassification()) {
+ mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_NEWS);
// change one of the reserved bundle channels to ensure changes are persisted across
// boot
updateNews = mHelper.getNotificationChannel(
@@ -643,7 +658,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
if (android.app.Flags.uiRichOngoing()) {
- mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true);
+ mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true, true);
}
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
@@ -657,6 +672,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
if (android.app.Flags.uiRichOngoing()) {
assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue();
+ assertThat(mXmlHelper.getAppLockedFields(PKG_N_MR1, UID_N_MR1) & USER_LOCKED_PROMOTABLE)
+ .isNotEqualTo(0);
}
assertEquals(channel1,
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
@@ -780,7 +797,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_oldXml_migrates() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -916,7 +933,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -975,7 +992,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ false, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1034,7 +1051,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1201,22 +1218,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\" uid=\"10002\">\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
+ "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1224,10 +1228,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "=\"false\" uid=\"10001\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1237,15 +1237,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1312,22 +1303,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\">\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
// Importance default because on in permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" "
@@ -1336,10 +1314,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1349,15 +1323,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1424,22 +1389,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\">\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
// Importance 0 because missing from permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"0\" show_badge=\"true\" "
@@ -1448,10 +1400,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1461,15 +1409,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1706,7 +1645,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// simulate load after reboot
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1761,7 +1700,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// simulate load after reboot
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1839,10 +1778,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -2174,10 +2113,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
+ assertEquals(Notification.PRIORITY_DEFAULT,
+ mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
assertEquals(VISIBILITY_NO_OVERRIDE,
mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
@@ -2538,9 +2477,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// Returns only non-deleted channels
List<NotificationChannel> channels =
- mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList();
+ mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true).getList();
// Default channel + non-deleted channel + system defaults
- assertEquals(notificationClassification() ? 6 : 2, channels.size());
+ assertEquals(2, channels.size());
for (NotificationChannel nc : channels) {
if (channel2.getId().equals(nc.getId())) {
compareChannels(channel2, nc);
@@ -2548,9 +2487,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
// Returns deleted channels too
- channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList();
+ channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true).getList();
// Includes system channel(s)
- assertEquals(notificationClassification() ? 7 : 3, channels.size());
+ assertEquals(3, channels.size());
for (NotificationChannel nc : channels) {
if (channel2.getId().equals(nc.getId())) {
compareChannels(channelMap.get(nc.getId()), nc);
@@ -2683,6 +2622,72 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void getPackagesBypassingDnd_noChannelsBypassing() throws Exception {
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_N_MR1))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesBypassingDnd_oneChannelBypassing_deleted() {
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setDeleted(true);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+ /*has DND access*/ true, UID_N_MR1, false);
+
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_N_MR1))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() {
+ int uid = UID_N_MR1;
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, uid, ncg, /* fromTargetApp */ true,
+ uid, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel1, true, /*has DND access*/ true,
+ uid, false);
+ ncg.setBlocked(true);
+
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(uid))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesBypassingDnd_multipleApps() {
+ List<ZenBypassingApp> expected = ImmutableList.of(
+ new ZenBypassingApp(PKG_O, true), new ZenBypassingApp(PKG_P, false));
+
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel4 = new NotificationChannel("id4", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(false);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(true);
+ channel4.setBypassDnd(false);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+ /*has DND access*/ true, UID_N_MR1, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, true,
+ UID_O, false);
+ mHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
+ UID_P, false);
+ mHelper.createNotificationChannel(PKG_P, UID_P, channel4, true, true,
+ UID_P, false);
+
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_O)))
+ .containsExactlyElementsIn(expected);
+ }
+
+ @Test
public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
@@ -2694,7 +2699,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
// create notification channel that can bypass dnd
@@ -2704,18 +2714,35 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
// delete channels
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2731,7 +2758,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
// Recreate a channel & now the app has dnd access granted and can set the bypass dnd field
@@ -2741,7 +2773,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2757,7 +2795,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
// create notification channel that can bypass dnd, using local app level settings
@@ -2767,18 +2810,35 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
// delete channels
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2790,7 +2850,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
// create notification channel that can bypass dnd, but app is blocked
@@ -2805,7 +2865,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2817,7 +2883,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
// create notification channel that can bypass dnd, but app is blocked
@@ -2827,7 +2893,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2839,7 +2911,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
// create notification channel that can bypass dnd, but app is blocked
@@ -2849,7 +2921,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2865,7 +2943,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
// update channel so it CAN bypass dnd:
@@ -2873,7 +2956,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel.setBypassDnd(true);
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
// update channel so it can't bypass dnd:
@@ -2881,7 +2970,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel.setBypassDnd(false);
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2891,10 +2986,16 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// RankingHelper should change to false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
+ } else {
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
+ }
resetZenModeHelper();
}
@@ -2902,9 +3003,14 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testSetupNewZenModeHelper_cannotBypass() {
// start notification policy off with mAreChannelsBypassingDnd = false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ if (android.app.Flags.modesUi()) {
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
+ } else {
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
+ }
resetZenModeHelper();
}
@@ -2955,7 +3061,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testOnlyHasDefaultChannel() throws Exception {
assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
@@ -2966,6 +3071,18 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testOnlyHasDefaultChannel_bundleExists() throws Exception {
+ mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_NEWS);
+ assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
+ assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
+
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false,
+ UID_N_MR1, false);
+ assertFalse(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
+ }
+
+ @Test
public void testCreateDeletedChannel() throws Exception {
long[] vibration = new long[]{100, 67, 145, 156};
NotificationChannel channel =
@@ -3046,6 +3163,64 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
+ public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() {
+ final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri");
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+
+ assertThrows(SecurityException.class,
+ () -> mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel,
+ true, false, UID_N_MR1, false));
+ assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true))
+ .isNull();
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
+ public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() {
+ final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound");
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+ assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
+ .getSound()).isEqualTo(sound);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
+ public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() {
+ final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound");
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+ assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
+ .getSound()).isEqualTo(sound);
+ }
+
+ @Test
public void testPermanentlyDeleteChannels() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -3060,7 +3235,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.permanentlyDeleteNotificationChannels(PKG_N_MR1, UID_N_MR1);
// Only default channel remains
- assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size());
+ assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true)
+ .getList().size());
}
@Test
@@ -3175,13 +3351,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// user 0 records remain
for (int i = 0; i < user0Uids.length; i++) {
- assertEquals(notificationClassification() ? 5 : 1,
- mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false)
+ assertEquals(1,
+ mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true)
.getList().size());
}
// user 1 records are gone
for (int i = 0; i < user1Uids.length; i++) {
- assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false)
+ assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false, true)
.getList().size());
}
}
@@ -3198,7 +3374,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
new int[]{UID_N_MR1}));
assertEquals(0, mHelper.getNotificationChannels(
- PKG_N_MR1, UID_N_MR1, true).getList().size());
+ PKG_N_MR1, UID_N_MR1, true, true).getList().size());
// Not deleted
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
@@ -3206,8 +3382,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM,
new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
- assertEquals(notificationClassification() ? 6 : 2,
- mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
+ assertEquals(2,
+ mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true)
+ .getList().size());
}
@Test
@@ -3266,7 +3443,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
- assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true).getList().size());
+ assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size());
assertEquals(0, mHelper.getNotificationChannelGroups(PKG_O, UID_O).size());
NotificationChannel channel = getChannel();
@@ -3279,8 +3456,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testRecordDefaults() throws Exception {
assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertEquals(notificationClassification() ? 5 : 1,
- mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
+ assertEquals(1,
+ mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true)
+ .getList().size());
}
@Test
@@ -3517,9 +3695,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false,
UID_N_MR1, false);
}
- if (notificationClassification()) {
- numChannels += 4;
- }
expectedChannels.put(pkgName, numChannels);
}
@@ -4741,10 +4916,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testTooManyChannels() {
int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
- if (notificationClassification()) {
- // reserved channels lower limit
- numToCreate -= 4;
- }
for (int i = 0; i < numToCreate; i++) {
NotificationChannel channel = new NotificationChannel(String.valueOf(i),
String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
@@ -4765,10 +4936,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testTooManyChannels_xml() throws Exception {
int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
- if (notificationClassification()) {
- // reserved channels lower limit
- numToCreate -= 4;
- }
String extraChannel = "EXTRA";
String extraChannel1 = "EXTRA1";
@@ -5786,9 +5953,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("expected number of events",
- notificationClassification() ? 7 : 3,
- events.size());
+ assertEquals("expected number of events", 3, events.size());
for (StatsEvent ev : events) {
// all of these events should be of PackageNotificationChannelPreferences type,
// and therefore we expect the atom to have this field.
@@ -5829,17 +5994,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_O, UID_O, channelC, true, false, UID_O, false);
List<String> channels = new LinkedList<>(Arrays.asList("a", "b", "c"));
- if (notificationClassification()) {
- channels.add(NEWS_ID);
- channels.add(PROMOTIONS_ID);
- channels.add(SOCIAL_MEDIA_ID);
- channels.add(RECS_ID);
- }
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events", notificationClassification() ? 7 : 3, events.size());
+ assertEquals("total events", 3, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5873,7 +6032,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.pullPackageChannelPreferencesStats(events);
// In this case, we want to check the properties of the conversation channel (not parent)
- assertEquals("total events", notificationClassification() ? 6 : 2, events.size());
+ assertEquals("total events", 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5905,9 +6064,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events",
- notificationClassification() ? 6 : 2,
- events.size());
+ assertEquals("total events", 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5938,9 +6095,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events",
- notificationClassification() ? 6 : 2,
- events.size());
+ assertEquals("total events", 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6224,26 +6379,73 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
- public void testNotificationBundles() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
+ public void testGetNotificationChannels_omitBundleChannels() {
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
- // verify 4 reserved channels are created
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, false).getList()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testNotificationBundles() {
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance())
.isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(1);
+
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_SOCIAL_MEDIA);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false).
+ getImportance()).isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(2);
+
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_CONTENT_RECOMMENDATION);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance())
+ .isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(3);
+
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_PROMOTION);
assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
.getImportance()).isEqualTo(IMPORTANCE_LOW);
- assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false)
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(4);
+
+ // only the first 4 types are created; no others
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_OTHER);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(4);
+ }
+
+ @Test
+ @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testNotificationBundles_off_deletesData() throws Exception {
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_P + "\" uid=\"" + UID_P + "\">\n"
+ + "<channel id=\"android.app.social\" name=\"Social\" importance=\"2\"/>\n"
+ + "<channel id=\"android.app.news\" name=\"News\" importance=\"2\"/>\n"
+ + "<channel id=\"android.app.recs\" name=\"Recs\" importance=\"2\"/>\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promos\" importance=\"2\"/>\n"
+ + "<channel id=\"keep.me\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package></ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+
+ // verify 4 reserved channels are created
+ assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, NEWS_ID, true)).isNull();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, PROMOTIONS_ID, true)).isNull();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true)).isNull();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, RECS_ID, true)).isNull();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, "keep.me", false)
.getImportance()).isEqualTo(IMPORTANCE_LOW);
- assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance())
- .isEqualTo(IMPORTANCE_LOW);
}
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testNotificationBundles_appsCannotUpdate() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
NotificationChannel fromApp =
new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
@@ -6256,8 +6458,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testNotificationBundles_osCanAllowToBypassDnd() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
NotificationChannel fromApp =
new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
@@ -6267,18 +6468,17 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception {
- mHelper.setShowBadge(PKG_P, UID_P, true);
// the public create/update methods should prevent this, so take advantage of the fact that
// the object is in the same process
- mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true);
+ mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_SOCIAL_MEDIA).setDeleted(true);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
UserHandle.USER_ALL, SOCIAL_MEDIA_ID);
loadStreamXml(baos, false, UserHandle.USER_ALL);
- assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).
- isDeleted()).isFalse();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, SOCIAL_MEDIA_ID, true)
+ .isDeleted()).isFalse();
}
@Test
@@ -6311,20 +6511,39 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
+ @DisableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
public void testNoAppHasPermissionToPromoteByDefault() {
mHelper.setShowBadge(PKG_P, UID_P, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+ android.app.Flags.FLAG_UI_RICH_ONGOING})
+ public void testAllAppsHavePermissionToPromoteByDefault() {
+ mHelper.setShowBadge(PKG_P, UID_P, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted() {
- mHelper.setCanBePromoted(PKG_P, UID_P, true);
+ mHelper.setCanBePromoted(PKG_P, UID_P, true, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
- mHelper.setCanBePromoted(PKG_P, UID_P, false);
+ mHelper.setCanBePromoted(PKG_P, UID_P, false, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
verify(mHandler, never()).requestSort();
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
+ public void testSetCanBePromoted_allowlistNotOverrideUser() {
+ mHelper.setCanBePromoted(PKG_P, UID_P, true, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+
+ mHelper.setCanBePromoted(PKG_P, UID_P, false, false);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 9a6e81865947..f90034614383 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -92,6 +92,7 @@ public class RankingHelperTest extends UiServiceTestCase {
@Mock ZenModeHelper mMockZenModeHelper;
@Mock RankingConfig mConfig;
@Mock Vibrator mVibrator;
+ @Mock GroupHelper mGroupHelper;
private NotificationManager.Policy mTestNotificationPolicy;
private Notification mNotiGroupGSortA;
@@ -154,10 +155,10 @@ public class RankingHelperTest extends UiServiceTestCase {
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()},
- mock(IPlatformCompat.class));
+ mock(IPlatformCompat.class), mGroupHelper);
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
index 65ed7b6e622d..934c33b7f1f1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.HOURS;
+import android.service.notification.RateEstimator;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index fe4ce465e9be..52c34889a1ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -1,18 +1,38 @@
package com.android.server.notification;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentFilter;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.SimpleClock;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.Condition;
import android.service.notification.ScheduleCalendar;
import android.service.notification.ZenModeConfig;
@@ -24,11 +44,20 @@ import androidx.test.filters.SmallTest;
import com.android.server.UiServiceTestCase;
import com.android.server.pm.PackageManagerService;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
@@ -36,17 +65,22 @@ import java.util.GregorianCalendar;
@SmallTest
@RunWithLooper
public class ScheduleConditionProviderTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- ScheduleConditionProvider mService;
+ private ScheduleConditionProvider mService;
+ private TestClock mClock = new TestClock();
+ @Mock private AlarmManager mAlarmManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
Intent startIntent =
new Intent("com.android.server.notification.ScheduleConditionProvider");
startIntent.setPackage("android");
- ScheduleConditionProvider service = new ScheduleConditionProvider();
+ ScheduleConditionProvider service = new ScheduleConditionProvider(mClock);
service.attach(
getContext(),
null, // ActivityThread not actually used in Service
@@ -57,7 +91,7 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase {
);
service.onCreate();
service.onBind(startIntent);
- mService = spy(service);
+ mService = service;
}
@Test
@@ -343,6 +377,87 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase {
assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onSubscribe_registersReceiverForAllUsers() {
+ Calendar now = getNow();
+ Uri condition = ZenModeConfig.toScheduleConditionId(getScheduleEndsInHour(now));
+
+ mService.onSubscribe(condition);
+
+ ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mContext).registerReceiverForAllUsers(any(), filterCaptor.capture(), any(), any());
+ IntentFilter filter = filterCaptor.getValue();
+ assertThat(filter.actionsIterator()).isNotNull();
+ assertThat(ImmutableList.copyOf(filter.actionsIterator()))
+ .contains(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onAlarmClockChanged_storesNextAlarm() {
+ Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z");
+ Instant scheduleEnd = scheduleStart.plus(1, HOURS);
+
+ Instant now = scheduleStart.plus(15, MINUTES);
+ mClock.setNowMillis(now.toEpochMilli());
+
+ Uri condition = ZenModeConfig.toScheduleConditionId(
+ getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault())));
+ mService.onSubscribe(condition);
+
+ // Now prepare to send an "alarm set for 16:30" broadcast.
+ Instant alarm = scheduleStart.plus(30, MINUTES);
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any());
+ BroadcastReceiver receiver = receiverCaptor.getValue();
+ receiver.setPendingResult(pendingResultForUserBroadcast(ActivityManager.getCurrentUser()));
+ when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(
+ new AlarmManager.AlarmClockInfo(alarm.toEpochMilli(), null));
+
+ Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ receiver.onReceive(mContext, intent);
+
+ // The time for the alarm was stored in the ScheduleCalendar, meaning the rule will end when
+ // the next evaluation after that point happens.
+ ScheduleCalendar scheduleCalendar =
+ mService.getSubscriptions().values().stream().findFirst().get();
+ assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() - 1)).isFalse();
+ assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() + 1)).isTrue();
+
+ // But the next wakeup is unchanged, at the time of the schedule end (17:00).
+ verify(mAlarmManager, times(2)).setExact(eq(RTC_WAKEUP), eq(scheduleEnd.toEpochMilli()),
+ any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+ public void onAlarmClockChanged_forAnotherUser_isIgnored() {
+ Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z");
+ Instant now = scheduleStart.plus(15, MINUTES);
+ mClock.setNowMillis(now.toEpochMilli());
+
+ Uri condition = ZenModeConfig.toScheduleConditionId(
+ getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault())));
+ mService.onSubscribe(condition);
+
+ // Now prepare to send an "alarm set for a different user" broadcast.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any());
+ BroadcastReceiver receiver = receiverCaptor.getValue();
+
+ reset(mAlarmManager);
+ int anotherUser = ActivityManager.getCurrentUser() + 1;
+ receiver.setPendingResult(pendingResultForUserBroadcast(anotherUser));
+ Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ receiver.onReceive(mContext, intent);
+
+ // The alarm data was not read.
+ verify(mAlarmManager, never()).getNextAlarmClock(anyInt());
+ }
+
private Calendar getNow() {
Calendar now = new GregorianCalendar();
now.set(Calendar.HOUR_OF_DAY, 14);
@@ -363,4 +478,38 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase {
info.endMinute = now.get(Calendar.MINUTE);
return info;
}
+
+ private static ZenModeConfig.ScheduleInfo getOneHourSchedule(ZonedDateTime start) {
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ // Note: DayOfWeek.MONDAY doesn't match Calendar.MONDAY
+ info.days = new int[] { (start.getDayOfWeek().getValue() % 7) + 1 };
+ info.startHour = start.getHour();
+ info.startMinute = start.getMinute();
+ info.endHour = start.plusHours(1).getHour();
+ info.endMinute = start.plusHours(1).getMinute();
+ info.exitAtAlarm = true;
+ return info;
+ }
+
+ private static BroadcastReceiver.PendingResult pendingResultForUserBroadcast(int userId) {
+ return new BroadcastReceiver.PendingResult(0, "", new Bundle(), 0, false, false, null,
+ userId, 0);
+ }
+
+ private static class TestClock extends SimpleClock {
+ private long mNowMillis = 441644400000L;
+
+ private TestClock() {
+ super(ZoneOffset.UTC);
+ }
+
+ @Override
+ public long millis() {
+ return mNowMillis;
+ }
+
+ private void setNowMillis(long millis) {
+ mNowMillis = millis;
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
index 5de323bc819c..949c5e2e1b0a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
@@ -69,6 +69,8 @@ public class SystemZenRulesTest extends UiServiceTestCase {
R.string.zen_mode_trigger_summary_range_symbol_combination, "%1$s-%2$s");
mContext.getOrCreateTestableResources().addOverride(
R.string.zen_mode_trigger_summary_divider_text, ",");
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.zen_mode_trigger_summary_combined, "%1$s,%2$s");
}
@Test
@@ -209,18 +211,30 @@ public class SystemZenRulesTest extends UiServiceTestCase {
}
@Test
- public void getShortDaysSummary_onlyDays() {
+ public void getDaysOfWeekShort_summarizesDays() {
ScheduleInfo scheduleInfo = new ScheduleInfo();
scheduleInfo.startHour = 10;
scheduleInfo.endHour = 16;
scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
- assertThat(SystemZenRules.getShortDaysSummary(mContext, scheduleInfo))
+ assertThat(SystemZenRules.getDaysOfWeekShort(mContext, scheduleInfo))
.isEqualTo("Mon-Fri");
}
@Test
+ public void getDaysOfWeekFull_summarizesDays() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+
+ assertThat(SystemZenRules.getDaysOfWeekFull(mContext, scheduleInfo))
+ .isEqualTo("Monday to Friday");
+ }
+
+ @Test
public void getTimeSummary_onlyTime() {
ScheduleInfo scheduleInfo = new ScheduleInfo();
scheduleInfo.startHour = 11;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 07d25dfd814e..ba91ca2323af 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -52,6 +52,14 @@ public class TestableNotificationManagerService extends NotificationManagerServi
}
public SensitiveLog lastSensitiveLog = null;
+ private static class ClassificationChannelLog {
+ public boolean hasPosted;
+ public boolean isAlerting;
+ public long classification;
+ public long lifetime;
+ }
+ public ClassificationChannelLog lastClassificationChannelLog = null;
+
TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
InstanceIdSequence notificationInstanceIdSequence) {
super(context, logger, notificationInstanceIdSequence);
@@ -211,4 +219,29 @@ public class TestableNotificationManagerService extends NotificationManagerServi
public interface ComponentPermissionChecker {
int check(String permission, int uid, int owningUid, boolean exported);
}
+
+ @Override
+ protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting,
+ int classification, int lifetimeMs) {
+ lastClassificationChannelLog = new ClassificationChannelLog();
+ lastClassificationChannelLog.hasPosted = hasPosted;
+ lastClassificationChannelLog.isAlerting = isAlerting;
+ lastClassificationChannelLog.classification = classification;
+ lastClassificationChannelLog.lifetime = lifetimeMs;
+ }
+
+ /**
+ * Returns true if the last recorded classification channel log has all the values specified.
+ */
+ public boolean checkLastClassificationChannelLog(boolean hasPosted, boolean isAlerting,
+ int classification, int lifetime) {
+ if (lastClassificationChannelLog == null) {
+ return false;
+ }
+
+ return hasPosted == lastClassificationChannelLog.hasPosted
+ && isAlerting == lastClassificationChannelLog.isAlerting
+ && classification == lastClassificationChannelLog.classification
+ && lifetime == lastClassificationChannelLog.lifetime;
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
index ad6c233789e9..09a6840350ad 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
@@ -19,6 +19,7 @@ import static com.android.server.notification.TimeToLiveHelper.EXTRA_KEY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -108,6 +109,21 @@ public class TimeToLiveHelperTest extends UiServiceTestCase {
}
@Test
+ public void testTimeoutExpiresButNotifEntryGone() {
+ NotificationRecord r = getRecord("testTimeoutExpires", 1);
+
+ mHelper.scheduleTimeoutLocked(r, 1);
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captor.capture());
+
+ mHelper.mKeys.clear();
+
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captor.getValue().getIntent());
+
+ verify(mNm, never()).timeoutNotification(anyString());
+ }
+
+ @Test
public void testTimeoutExpires_twoEntries() {
NotificationRecord first = getRecord("testTimeoutFirst", 1);
NotificationRecord later = getRecord("testTimeoutSecond", 2);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 84c4f620f394..3236f9501324 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -17,6 +17,8 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
+import static android.app.Flags.FLAG_MODES_API;
import static android.app.Flags.FLAG_MODES_UI;
import static android.app.Flags.modesUi;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
@@ -24,6 +26,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.suppressedEffectsToString;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.SOURCE_UNKNOWN;
@@ -52,17 +56,22 @@ import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.app.NotificationManager.Policy;
+import android.app.backup.BackupRestoreEventLogger;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Parcel;
+import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
@@ -135,7 +144,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
- FLAG_MODES_UI);
+ FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING);
}
public ZenModeConfigTest(FlagsParameterization flags) {
@@ -144,7 +153,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
@Before
public final void setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPm);
}
@@ -515,6 +523,98 @@ public class ZenModeConfigTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING})
+ public void testBackupRestore_fromPreModesUi() throws IOException, XmlPullParserException {
+ String xml = "<zen version=\"12\">\n"
+ + "<allow calls=\"true\" repeatCallers=\"true\" messages=\"true\""
+ + " reminders=\"false\" events=\"false\" callsFrom=\"2\" messagesFrom=\"2\""
+ + " alarms=\"true\" media=\"true\" system=\"false\" convos=\"true\""
+ + " convosFrom=\"2\" priorityChannelsAllowed=\"true\" />\n"
+ + "<disallow visualEffects=\"157\" />\n"
+ + "<manual enabled=\"true\" zen=\"1\" creationTime=\"0\" modified=\"false\" />\n"
+ + "<state areChannelsBypassingDnd=\"true\" />\n"
+ + "</zen>";
+
+ BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class);
+ readConfigXml(new ByteArrayInputStream(xml.getBytes()), logger);
+
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_RULES, 1);
+ }
+
+ @Test
+ public void testBackupRestore() throws IOException, XmlPullParserException {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.configurationActivity = CONFIG_ACTIVITY;
+ rule.component = OWNER;
+ rule.conditionId = CONDITION_ID;
+ rule.condition = CONDITION;
+ rule.enabled = ENABLED;
+ rule.creationTime = 123;
+ rule.id = "id";
+ rule.zenMode = INTERRUPTION_FILTER;
+ rule.modified = true;
+ rule.name = NAME;
+ rule.setConditionOverride(OVERRIDE_DEACTIVATE);
+ rule.pkg = OWNER.getPackageName();
+ rule.zenPolicy = POLICY;
+
+ rule.allowManualInvocation = ALLOW_MANUAL;
+ rule.type = TYPE;
+ rule.userModifiedFields = 16;
+ rule.zenPolicyUserModifiedFields = 5;
+ rule.zenDeviceEffectsUserModifiedFields = 2;
+ rule.iconResName = ICON_RES_NAME;
+ rule.triggerDescription = TRIGGER_DESC;
+ rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
+ if (Flags.modesUi()) {
+ rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+ }
+ config.automaticRules.put(rule.id, rule);
+
+ BackupRestoreEventLogger logger = null;
+ if (Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_API, true, baos, logger);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais, logger);
+
+ ZenModeConfig.ZenRule ruleActual = fromXml.automaticRules.get(rule.id);
+ assertEquals(rule.pkg, ruleActual.pkg);
+ assertEquals(OVERRIDE_NONE, ruleActual.getConditionOverride());
+ assertEquals(rule.enabler, ruleActual.enabler);
+ assertEquals(rule.component, ruleActual.component);
+ assertEquals(rule.configurationActivity, ruleActual.configurationActivity);
+ assertEquals(rule.condition, ruleActual.condition);
+ assertEquals(rule.enabled, ruleActual.enabled);
+ assertEquals(rule.creationTime, ruleActual.creationTime);
+ assertEquals(rule.modified, ruleActual.modified);
+ assertEquals(rule.conditionId, ruleActual.conditionId);
+ assertEquals(rule.name, ruleActual.name);
+ assertEquals(rule.zenMode, ruleActual.zenMode);
+
+ assertEquals(rule.allowManualInvocation, ruleActual.allowManualInvocation);
+ assertEquals(rule.iconResName, ruleActual.iconResName);
+ assertEquals(rule.type, ruleActual.type);
+ assertEquals(rule.userModifiedFields, ruleActual.userModifiedFields);
+ assertEquals(rule.zenPolicyUserModifiedFields, ruleActual.zenPolicyUserModifiedFields);
+ assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+ ruleActual.zenDeviceEffectsUserModifiedFields);
+ assertEquals(rule.triggerDescription, ruleActual.triggerDescription);
+ assertEquals(rule.zenPolicy, ruleActual.zenPolicy);
+ assertEquals(rule.deletionInstant, ruleActual.deletionInstant);
+ if (Flags.modesUi()) {
+ assertEquals(rule.disabledOrigin, ruleActual.disabledOrigin);
+ }
+ if (Flags.backupRestoreLogging()) {
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2);
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_RULES, 2);
+ }
+ }
+
+ @Test
public void testWriteToParcel() {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = CONFIG_ACTIVITY;
@@ -1010,7 +1110,39 @@ public class ZenModeConfigTest extends UiServiceTestCase {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
- public void testConfigXml_manualRule_upgradeWhenExisting() throws Exception {
+ public void testConfigXml_manualRuleWithoutCondition_upgradeWhenExisting() throws Exception {
+ // prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
+ // data on it because it's meant to indicate that the manual rule is on by merely existing.
+ ZenModeConfig config = new ZenModeConfig();
+ config.manualRule = new ZenModeConfig.ZenRule();
+ config.manualRule.enabled = true;
+ config.manualRule.pkg = "android";
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ config.manualRule.conditionId = null;
+ config.manualRule.enabler = "test";
+
+ // write out entire config xml
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos, null);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais, null);
+
+
+ // The result should be valid and contain a manual rule; the rule should have a non-null
+ // ZenPolicy and a condition whose state is true. The conditionId should be default.
+ assertThat(fromXml.isValid()).isTrue();
+ assertThat(fromXml.manualRule).isNotNull();
+ assertThat(fromXml.manualRule.zenPolicy).isNotNull();
+ assertThat(fromXml.manualRule.condition).isNotNull();
+ assertThat(fromXml.manualRule.condition.state).isEqualTo(STATE_TRUE);
+ assertThat(fromXml.manualRule.conditionId).isEqualTo(Uri.EMPTY);
+ assertThat(fromXml.manualRule.enabler).isEqualTo("test");
+ assertThat(fromXml.isManualActive()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testConfigXml_manualRuleWithCondition_upgradeWhenExisting() throws Exception {
// prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
// data on it because it's meant to indicate that the manual rule is on by merely existing.
ZenModeConfig config = new ZenModeConfig();
@@ -1023,12 +1155,13 @@ public class ZenModeConfigTest extends UiServiceTestCase {
// write out entire config xml
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
// The result should have a manual rule; it should have a non-null ZenPolicy and a condition
// whose state is true. The conditionId and enabler data should also be preserved.
+ assertThat(fromXml.isValid()).isTrue();
assertThat(fromXml.manualRule).isNotNull();
assertThat(fromXml.manualRule.zenPolicy).isNotNull();
assertThat(fromXml.manualRule.condition).isNotNull();
@@ -1051,9 +1184,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
// write out entire config xml
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
// The result should have a manual rule; it should not be changed from the previous rule.
assertThat(fromXml.manualRule).isEqualTo(config.manualRule);
@@ -1180,9 +1313,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
config.manualRule.enabled = false;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
assertThat(fromXml.manualRule.enabled).isTrue();
}
@@ -1326,23 +1459,23 @@ public class ZenModeConfigTest extends UiServiceTestCase {
}
private void writeConfigXml(ZenModeConfig config, Integer version, boolean forBackup,
- ByteArrayOutputStream os) throws IOException {
+ ByteArrayOutputStream os, BackupRestoreEventLogger logger) throws IOException {
String tag = ZEN_TAG;
TypedXmlSerializer out = Xml.newFastSerializer();
out.setOutput(new BufferedOutputStream(os), "utf-8");
out.startDocument(null, true);
out.startTag(null, tag);
- config.writeXml(out, version, forBackup);
+ config.writeXml(out, version, forBackup, logger);
out.endTag(null, tag);
out.endDocument();
}
- private ZenModeConfig readConfigXml(ByteArrayInputStream is)
+ private ZenModeConfig readConfigXml(ByteArrayInputStream is, BackupRestoreEventLogger logger)
throws XmlPullParserException, IOException {
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(is), null);
parser.nextTag();
- return ZenModeConfig.readXml(parser);
+ return ZenModeConfig.readXml(parser, logger);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 91eb2edeee13..c6cc941ba1cd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -60,6 +60,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
@@ -69,6 +70,7 @@ import java.util.Set;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
@@ -127,7 +129,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.ZenRule.class, getZenRuleExemptFields());
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
@@ -145,6 +147,337 @@ public class ZenModeDiffTest extends UiServiceTestCase {
}
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toStringNoChangeAddRemove() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{no changes}");
+
+ d = new ZenModeDiff.RuleDiff(r1, null);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{removed}");
+
+ d = new ZenModeDiff.RuleDiff(null, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{added}");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toString() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{"
+ + "enabled:true->false, "
+ + "conditionOverride:2->1, "
+ + "name:string1->string2, "
+ + "zenMode:2->1, "
+ + "conditionId:null->, "
+ + "condition:null->Condition["
+ + "state=STATE_TRUE,"
+ + "id=hello:,"
+ + "summary=,"
+ + "line1=,"
+ + "line2=,"
+ + "icon=-1,"
+ + "source=SOURCE_UNKNOWN,"
+ + "flags=2], "
+ + "component:null->ComponentInfo{b/b}, "
+ + "configurationActivity:null->ComponentInfo{a/a}, "
+ + "id:string1->string2, "
+ + "creationTime:200->100, "
+ + "enabler:string1->string2, "
+ + "zenPolicy:ZenPolicyDiff{"
+ + "mPriorityCategories_Reminders:1->2, "
+ + "mPriorityCategories_Events:1->2, "
+ + "mPriorityCategories_Messages:1->2, "
+ + "mPriorityCategories_Calls:1->2, "
+ + "mPriorityCategories_RepeatCallers:1->2, "
+ + "mPriorityCategories_Alarms:1->2, "
+ + "mPriorityCategories_Media:1->2, "
+ + "mPriorityCategories_System:1->2, "
+ + "mPriorityCategories_Conversations:1->2, "
+ + "mVisualEffects_FullScreenIntent:1->2, "
+ + "mVisualEffects_Lights:1->2, "
+ + "mVisualEffects_Peek:1->2, "
+ + "mVisualEffects_StatusBar:1->2, "
+ + "mVisualEffects_Badge:1->2, "
+ + "mVisualEffects_Ambient:1->2, "
+ + "mVisualEffects_NotificationList:1->2, "
+ + "mPriorityMessages:2->1, "
+ + "mPriorityCalls:2->1, "
+ + "mConversationSenders:2->1, "
+ + "mAllowChannels:2->1}, "
+ + "modified:true->false, "
+ + "pkg:string1->string2, "
+ + "zenDeviceEffects:ZenDeviceEffectsDiff{"
+ + "mGrayscale:true->false, "
+ + "mSuppressAmbientDisplay:true->false, "
+ + "mDimWallpaper:true->false, "
+ + "mNightMode:true->false, "
+ + "mDisableAutoBrightness:true->false, "
+ + "mDisableTapToWake:true->false, "
+ + "mDisableTiltToWake:true->false, "
+ + "mDisableTouch:true->false, "
+ + "mMinimizeRadioUsage:true->false, "
+ + "mMaximizeDoze:true->false, "
+ + "mExtraEffects:[effect1]->[effect2]}, "
+ + "triggerDescription:string1->string2, "
+ + "type:2->1, "
+ + "allowManualInvocation:true->false, "
+ + "iconResName:string1->string2, "
+ + "legacySuppressedEffects:2->1}");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toStringNullStartPolicy() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ // Create a ZenRule with ZenDeviceEffects and ZenPolicy as null.
+ r1.zenPolicy = null;
+ r1.zenDeviceEffects = null;
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{"
+ + "enabled:true->false, "
+ + "conditionOverride:2->1, "
+ + "name:string1->string2, "
+ + "zenMode:2->1, "
+ + "conditionId:null->, "
+ + "condition:null->Condition["
+ + "state=STATE_TRUE,"
+ + "id=hello:,"
+ + "summary=,"
+ + "line1=,"
+ + "line2=,"
+ + "icon=-1,"
+ + "source=SOURCE_UNKNOWN,"
+ + "flags=2], "
+ + "component:null->ComponentInfo{b/b}, "
+ + "configurationActivity:null->ComponentInfo{a/a}, "
+ + "id:string1->string2, "
+ + "creationTime:200->100, "
+ + "enabler:string1->string2, "
+ + "zenPolicy:ZenPolicyDiff{added}, "
+ + "modified:true->false, "
+ + "pkg:string1->string2, "
+ + "zenDeviceEffects:ZenDeviceEffectsDiff{added}, "
+ + "triggerDescription:string1->string2, "
+ + "type:2->1, "
+ + "allowManualInvocation:true->false, "
+ + "iconResName:string1->string2, "
+ + "legacySuppressedEffects:2->1}");
+ }
+
+ @Test
+ public void testDeviceEffectsDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder().build();
+
+ // Both sides same rule
+ ZenModeDiff.DeviceEffectsDiff dSame = new ZenModeDiff.DeviceEffectsDiff(effects, effects);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.DeviceEffectsDiff deleted = new ZenModeDiff.DeviceEffectsDiff(effects, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.DeviceEffectsDiff added = new ZenModeDiff.DeviceEffectsDiff(null, effects);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testDeviceEffectsDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.DeviceEffectsDiff d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testDeviceEffectsDiff_toString() throws Exception {
+ // Ensure device effects toString is readable.
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+
+ ZenModeDiff.DeviceEffectsDiff d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{no changes}");
+
+ d = new ZenModeDiff.DeviceEffectsDiff(effects1, null);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{removed}");
+
+ d = new ZenModeDiff.DeviceEffectsDiff(null, effects2);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{added}");
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+
+ d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{"
+ + "mGrayscale:true->false, "
+ + "mSuppressAmbientDisplay:true->false, "
+ + "mDimWallpaper:true->false, "
+ + "mNightMode:true->false, "
+ + "mDisableAutoBrightness:true->false, "
+ + "mDisableTapToWake:true->false, "
+ + "mDisableTiltToWake:true->false, "
+ + "mDisableTouch:true->false, "
+ + "mMinimizeRadioUsage:true->false, "
+ + "mMaximizeDoze:true->false, "
+ + "mExtraEffects:[effect1]->[effect2]}");
+ }
+
+
+ @Test
+ public void testPolicyDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenPolicy effects = new ZenPolicy.Builder().build();
+
+ // Both sides same rule
+ ZenModeDiff.PolicyDiff dSame = new ZenModeDiff.PolicyDiff(effects, effects);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.PolicyDiff deleted = new ZenModeDiff.PolicyDiff(effects, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.PolicyDiff added = new ZenModeDiff.PolicyDiff(null, effects);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testPolicyDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(ZenPolicy.class, Collections.emptySet(),
+ false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.PolicyDiff d = new ZenModeDiff.PolicyDiff(policy1, policy2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff.
+ // Because ZenPolicy consolidates priority category and visual effect fields in a list,
+ // we cannot use reflection on ZenPolicy to get the list of fields.
+ ArrayList<String> diffFields = new ArrayList<>();
+ Field[] fields = ZenModeDiff.PolicyDiff.class.getDeclaredFields();
+
+ for (Field field : fields) {
+ int m = field.getModifiers();
+ if (Modifier.isStatic(m) && Modifier.isFinal(m)) {
+ diffFields.add((String) field.get(policy1));
+ }
+ }
+
+ for (String name : diffFields) {
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testPolicyDiff_toString() throws Exception {
+ // Ensure device effects toString is readable.
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+
+ ZenModeDiff.PolicyDiff d = new ZenModeDiff.PolicyDiff(policy1, policy2);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{no changes}");
+
+ d = new ZenModeDiff.PolicyDiff(policy1, null);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{removed}");
+
+ d = new ZenModeDiff.PolicyDiff(null, policy2);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{added}");
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenPolicy.class, Collections.emptySet() /*no exempt fields*/, false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom, expectedTo);
+
+ d = new ZenModeDiff.PolicyDiff(policy1, policy2);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{"
+ + "mPriorityCategories_Reminders:1->2, "
+ + "mPriorityCategories_Events:1->2, "
+ + "mPriorityCategories_Messages:1->2, "
+ + "mPriorityCategories_Calls:1->2, "
+ + "mPriorityCategories_RepeatCallers:1->2, "
+ + "mPriorityCategories_Alarms:1->2, "
+ + "mPriorityCategories_Media:1->2, "
+ + "mPriorityCategories_System:1->2, "
+ + "mPriorityCategories_Conversations:1->2, "
+ + "mVisualEffects_FullScreenIntent:1->2, "
+ + "mVisualEffects_Lights:1->2, "
+ + "mVisualEffects_Peek:1->2, "
+ + "mVisualEffects_StatusBar:1->2, "
+ + "mVisualEffects_Badge:1->2, "
+ + "mVisualEffects_Ambient:1->2, "
+ + "mVisualEffects_NotificationList:1->2, "
+ + "mPriorityMessages:2->1, "
+ + "mPriorityCalls:2->1, "
+ + "mConversationSenders:2->1, "
+ + "mAllowChannels:2->1}");
+ }
+
private static Set<String> getZenRuleExemptFields() {
// "Metadata" fields are never compared.
Set<String> exemptFields = new LinkedHashSet<>(
@@ -194,7 +527,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.class, getConfigExemptAndFlaggedFields());
+ ZenModeConfig.class, getConfigExemptAndFlaggedFields(), false);
generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
@@ -223,7 +556,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+ ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS, false);
generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
@@ -359,17 +692,23 @@ public class ZenModeDiffTest extends UiServiceTestCase {
// Get the fields on which we would want to check a diff. The requirements are: not final or/
// static (as these should/can never change), and not in a specific list that's exempted.
- private List<Field> getFieldsForDiffCheck(Class<?> c, Set<String> exemptNames)
+ private List<Field> getFieldsForDiffCheck(Class<?> c, Set<String> exemptNames,
+ boolean includeFinal)
throws SecurityException {
Field[] fields = c.getDeclaredFields();
ArrayList<Field> out = new ArrayList<>();
for (Field field : fields) {
// Check for exempt reasons
+ // Anything in provided exemptNames is skipped.
+ if (exemptNames.contains(field.getName())) {
+ continue;
+ }
int m = field.getModifiers();
- if (Modifier.isFinal(m)
- || Modifier.isStatic(m)
- || exemptNames.contains(field.getName())) {
+ if (Modifier.isStatic(m)) {
+ continue;
+ }
+ if (!includeFinal && Modifier.isFinal(m)) {
continue;
}
out.add(field);
@@ -377,6 +716,106 @@ public class ZenModeDiffTest extends UiServiceTestCase {
return out;
}
+ // Generate a set of diffs for two ZenPolicy objects. Store the results in the provided
+ // expectation maps.
+ private void generateFieldDiffsForZenPolicy(ZenPolicy a, ZenPolicy b, List<Field> fields,
+ ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+ throws Exception {
+ // Loop through fields for which we want to check diffs, set a diff and keep track of
+ // what we set.
+ for (Field f : fields) {
+ f.setAccessible(true);
+ // Just double-check also that the fields actually are for the class declared
+ assertEquals(f.getDeclaringClass(), a.getClass());
+ Class<?> t = f.getType();
+
+ if (int.class.equals(t)) {
+ // these will not be valid for arbitrary int enums, but should suffice for a diff.
+ f.setInt(a, 2);
+ expectedA.put(f.getName(), 2);
+ f.setInt(b, 1);
+ expectedB.put(f.getName(), 1);
+ } else if (List.class.equals(t)) {
+ // Fieds mPriorityCategories and mVisualEffects store multiple values and
+ // must be treated separately.
+ List<Integer> aList = (ArrayList<Integer>) f.get(a);
+ List<Integer> bList = (ArrayList<Integer>) f.get(b);
+ if (f.getName().equals("mPriorityCategories")) {
+ // PRIORITY_CATEGORY_REMINDERS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 0,
+ "mPriorityCategories_Reminders");
+ // PRIORITY_CATEGORY_EVENTS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 1,
+ "mPriorityCategories_Events");
+ // PRIORITY_CATEGORY_MESSAGES
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 2,
+ "mPriorityCategories_Messages");
+ // PRIORITY_CATEGORY_CALLS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 3,
+ "mPriorityCategories_Calls");
+ // PRIORITY_CATEGORY_REPEAT_CALLERS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 4,
+ "mPriorityCategories_RepeatCallers");
+ // PRIORITY_CATEGORY_ALARMS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 5,
+ "mPriorityCategories_Alarms");
+ // PRIORITY_CATEGORY_MEDIA
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 6,
+ "mPriorityCategories_Media");
+ // PRIORITY_CATEGORY_SYSTEM
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 7,
+ "mPriorityCategories_System");
+ // PRIORITY_CATEGORY_CONVERSATIONS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 8,
+ "mPriorityCategories_Conversations");
+ // Assert that we've set every PriorityCategory enum value.
+ assertThat(Collections.frequency(aList, ZenPolicy.STATE_ALLOW))
+ .isEqualTo(ZenPolicy.NUM_PRIORITY_CATEGORIES);
+ } else if (f.getName().equals("mVisualEffects")) {
+ // VISUAL_EFFECT_FULL_SCREEN_INTENT
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 0,
+ "mVisualEffects_FullScreenIntent");
+ // VISUAL_EFFECT_LIGHTS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 1,
+ "mVisualEffects_Lights");
+ // VISUAL_EFFECT_PEEK
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 2,
+ "mVisualEffects_Peek");
+ // VISUAL_EFFECT_STATUS_BAR
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 3,
+ "mVisualEffects_StatusBar");
+ // VISUAL_EFFECT_BADGE
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 4,
+ "mVisualEffects_Badge");
+ // VISUAL_EFFECT_AMBIENT
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 5,
+ "mVisualEffects_Ambient");
+ // VISUAL_EFFECT_NOTIFICATION_LIST
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 6,
+ "mVisualEffects_NotificationList");
+ // Assert that we've set every VisualeEffect enum value.
+ assertThat(Collections.frequency(aList, ZenPolicy.STATE_ALLOW))
+ .isEqualTo(ZenPolicy.NUM_VISUAL_EFFECTS);
+ } else {
+ // Any other lists that are added should be added to the diff.
+ fail("could not generate field diffs for policy list: " + f.getName());
+ }
+ }
+ }
+ }
+
+ // Helper function to create a diff in two list values at a given index, and record that
+ // diff's values in the associated expected maps under the provided field name.
+ private void setPolicyListValueDiff(List<Integer> aList, List<Integer> bList,
+ ArrayMap<String, Object> expectedA,
+ ArrayMap<String, Object> expectedB,
+ int index, String fieldName) {
+ aList.set(index, ZenPolicy.STATE_ALLOW);
+ expectedA.put(fieldName, ZenPolicy.STATE_ALLOW);
+ bList.set(index, ZenPolicy.STATE_DISALLOW);
+ expectedB.put(fieldName, ZenPolicy.STATE_DISALLOW);
+ }
+
// Generate a set of generic diffs for the specified two objects and the fields to generate
// diffs for, and store the results in the provided expectation maps to be able to check the
// output later.
@@ -420,6 +859,44 @@ public class ZenModeDiffTest extends UiServiceTestCase {
expectedA.put(f.getName(), "string1");
f.set(b, "string2");
expectedB.put(f.getName(), "string2");
+ } else if (Set.class.equals(t)) {
+ Set<String> aSet = Set.of("effect1");
+ Set<String> bSet = Set.of("effect2");
+ f.set(a, aSet);
+ expectedA.put(f.getName(), aSet);
+ f.set(b, bSet);
+ expectedB.put(f.getName(), bSet);
+ } else if (ZenDeviceEffects.class.equals(t)) {
+ // Recurse into generating field diffs for ZenDeviceEffects.
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+ f.set(a, effects1);
+ expectedA.put(f.getName(), effects1);
+ f.set(b, effects2);
+ expectedB.put(f.getName(), effects2);
+ } else if (ZenPolicy.class.equals(t)) {
+ // Recurse into generating field diffs for ZenPolicy.
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(ZenPolicy.class,
+ Collections.emptySet(), false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom,
+ expectedTo);
+ f.set(a, policy1);
+ expectedA.put(f.getName(), policy1);
+ f.set(b, policy2);
+ expectedB.put(f.getName(), policy2);
} else {
// catch-all for other types: have the field be "added"
f.set(a, null);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index b997f5d9a2a0..a49f5a89b11b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -26,8 +26,6 @@ import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSAT
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
@@ -57,7 +55,6 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.server.UiServiceTestCase;
@@ -188,49 +185,6 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
}
@Test
- public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()
- - SUPPRESSED_EFFECT_STATUS_BAR, 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongId() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongPackage() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android2");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_no() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
- }
-
- @Test
public void testSuppressAnything_yes_ZenModeOff() {
NotificationRecord r = getNotificationRecord();
when(r.getSbn().getPackageName()).thenReturn("bananas");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index d4cba8d726fb..09da0156eb82 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -20,7 +20,9 @@ import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
+import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
import static android.app.Flags.FLAG_MODES_API;
+import static android.app.Flags.FLAG_MODES_MULTIUSER;
import static android.app.Flags.FLAG_MODES_UI;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -47,6 +49,8 @@ import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -61,12 +65,14 @@ import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.ZenModeConfig.ORIGIN_APP;
import static android.service.notification.ZenModeConfig.ORIGIN_INIT;
import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_SYSTEM;
import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE;
+import static android.service.notification.ZenModeConfig.implicitRuleId;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
@@ -97,11 +103,13 @@ import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.notNull;
@@ -116,15 +124,17 @@ import static org.mockito.Mockito.withSettings;
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.compat.CompatChanges;
import android.content.ComponentName;
-import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -169,7 +179,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.config.sysui.TestableFlagResolver;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.os.AtomsProto;
@@ -272,7 +281,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
private TestableLooper mTestableLooper;
private final TestClock mTestClock = new TestClock();
private ZenModeHelper mZenModeHelper;
- private ContentResolver mContentResolver;
@Mock
DeviceEffectsApplier mDeviceEffectsApplier;
@Mock
@@ -283,8 +291,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.progressionOf(FLAG_MODES_API,
- FLAG_MODES_UI);
+ return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING);
}
public ZenModeHelperTest(FlagsParameterization flags) {
@@ -297,7 +304,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mTestableLooper = TestableLooper.get(this);
mContext.ensureTestableResources();
- mContentResolver = mContext.getContentResolver();
mResources = mock(Resources.class, withSettings()
.spiedInstance(mContext.getResources()));
mPkg = mContext.getPackageName();
@@ -315,11 +321,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mContext.addMockSystemService(AppOpsManager.class, mAppOps);
mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+ mContext.addMockSystemService(Context.ALARM_SERVICE, mock(AlarmManager.class));
mConditionProviders = new ConditionProviders(mContext, new UserProfiles(),
AppGlobals.getPackageManager());
- mConditionProviders.addSystemProvider(new CountdownConditionProvider());
- mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
+ CountdownConditionProvider countdown = spy(new CountdownConditionProvider());
+ ScheduleConditionProvider schedule = spy(new ScheduleConditionProvider());
+ doNothing().when(countdown).notifyConditions(any());
+ doNothing().when(schedule).notifyConditions(any());
+ mConditionProviders.addSystemProvider(countdown);
+ mConditionProviders.addSystemProvider(schedule);
mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(), mTestClock,
mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
@@ -378,7 +389,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
serializer.startDocument(null, true);
- mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
+ mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL, null);
serializer.endDocument();
serializer.flush();
mZenModeHelper.setConfig(new ZenModeConfig(), null, ORIGIN_INIT, "writing xml",
@@ -386,13 +397,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
return baos;
}
- private ByteArrayOutputStream writeXmlAndPurgeForUser(Integer version, int userId)
+ private ByteArrayOutputStream writeXmlAndPurgeForUser(Integer version, int userId,
+ boolean forBackup, BackupRestoreEventLogger logger)
throws Exception {
TypedXmlSerializer serializer = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
serializer.startDocument(null, true);
- mZenModeHelper.writeXml(serializer, true, version, userId);
+ mZenModeHelper.writeXml(serializer, forBackup, version, userId, logger);
serializer.endDocument();
serializer.flush();
ZenModeConfig newConfig = new ZenModeConfig();
@@ -483,6 +495,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ public void testZenOn_RepeatCallers_CallTypesBlocked() {
+ mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
+ // Any call allowed but no repeat callers
+ mZenModeHelper.mConsolidatedPolicy = new Policy(PRIORITY_CATEGORY_CALLS,
+ PRIORITY_SENDERS_ANY, 0, 0, 0);
+ mZenModeHelper.applyRestrictions();
+
+ verifyApplyRestrictions(true, true,
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+ verifyApplyRestrictions(true, true,
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST);
+ }
+
+
+ @Test
public void testZenOn_StarredCallers_CallTypesBlocked() {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
@@ -491,7 +519,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
| PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
| PRIORITY_CATEGORY_CONVERSATIONS | PRIORITY_CATEGORY_CALLS
| PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_EVENTS | PRIORITY_CATEGORY_REMINDERS
- | PRIORITY_CATEGORY_SYSTEM,
+ | PRIORITY_CATEGORY_SYSTEM | PRIORITY_CATEGORY_REPEAT_CALLERS,
PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_ANY, 0, CONVERSATION_SENDERS_ANYONE);
mZenModeHelper.applyRestrictions();
@@ -690,13 +718,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azr, ORIGIN_SYSTEM, "reason", SYSTEM_UID);
// Enable rule
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
- new Condition(azr.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
SYSTEM_UID);
// Confirm that the consolidated policy doesn't allow anything
@@ -724,13 +751,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azr, ORIGIN_SYSTEM, "reason", SYSTEM_UID);
// Enable rule
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
- new Condition(azr.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
SYSTEM_UID);
// Confirm that the consolidated policy allows only alarms and media and nothing else
@@ -747,54 +773,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- public void testZenUpgradeNotification() {
- /**
- * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
- * notification on watches. So, assume that the device is not watch.
- */
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
-
- // shows zen upgrade notification if stored settings says to shows,
- // zen has not been updated, boot is completed
- // and we're setting zen mode on
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- assertEquals(0, Settings.Secure.getInt(mContentResolver,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, -1));
- }
-
- @Test
- public void testNoZenUpgradeNotification() {
- // doesn't show upgrade notification if stored settings says don't show
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
- public void testNoZenUpgradeNotificationZenUpdated() {
- // doesn't show upgrade notification since zen was already updated
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
public void testZenSetInternalRinger_AllPriorityNotificationSoundsMuted() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
@@ -805,7 +783,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
Policy totalSilence = new Policy(0, 0, 0);
- mZenModeHelper.setNotificationPolicy(totalSilence, ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, totalSilence, ORIGIN_APP, 1);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
// 2. verify ringer is unchanged
@@ -842,8 +820,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void testRingerAffectedStreamsPriorityOnly() {
// in priority only mode:
// ringtone, notification and system streams are affected by ringer mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_APP, "test", "caller", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -859,9 +837,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// even when ringer is muted (since all ringer sounds cannot bypass DND),
// system stream is still affected by ringer mode
- mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), ORIGIN_APP, 1);
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_APP, "test", "caller", 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, new Policy(0, 0, 0), ORIGIN_APP,
+ 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -968,7 +947,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// apply zen off multiple times - verify ringer is not set to normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
for (int i = 0; i < 3; i++) {
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
@@ -993,7 +972,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
@@ -1018,7 +997,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
@@ -1034,9 +1013,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
reset(mAudioManager);
// Turn manual zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ORIGIN_APP,
- null, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP, null, "test", CUSTOM_PKG_UID);
// audio manager shouldn't do anything until the handler processes its messages
verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
@@ -1061,6 +1039,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Turn manual zen mode on
mZenModeHelper.setManualZenMode(
+ UserHandle.CURRENT,
ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null,
ORIGIN_APP,
@@ -1068,6 +1047,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
"test",
CUSTOM_PKG_UID);
mZenModeHelper.setManualZenMode(
+ UserHandle.CURRENT,
ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null,
ORIGIN_APP,
@@ -1091,19 +1071,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testParcelConfig() {
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_EVENTS
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
| PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
ORIGIN_UNKNOWN,
1);
- mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .setShouldDisplayGrayscale(true)
- .setShouldUseNightMode(true)
- .build(), ORIGIN_UNKNOWN, "test", 1);
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_UNKNOWN, "test", "me", 1);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT,
+ new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
+ .build(),
+ ORIGIN_UNKNOWN, "test", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig actual = mZenModeHelper.mConfig.copy();
@@ -1112,18 +1095,21 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testWriteXml() throws Exception {
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_EVENTS
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
| PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
CONVERSATION_SENDERS_ANYONE),
ORIGIN_UNKNOWN, 1);
- mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .setShouldDisplayGrayscale(true)
- .build(), ORIGIN_UNKNOWN, "test", 1);
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_UNKNOWN, "test", "me", 1);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT,
+ new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .build(),
+ ORIGIN_UNKNOWN, "test", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
if (Flags.modesUi()) {
@@ -1134,7 +1120,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertEquals("Config mismatch: current vs expected: "
+ new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected,
@@ -1143,9 +1129,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testProto() throws InvalidProtocolBufferException {
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM,
- null, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, null,
+ "test", CUSTOM_PKG_UID);
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
@@ -1210,7 +1196,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1368,8 +1354,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, CUSTOM_RULE_ID, ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
assertTrue(-1
== mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
}
@@ -1397,9 +1383,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void testProtoWithManualRule() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
- mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY,
- ORIGIN_APP,
- "test", "me", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY,
+ ORIGIN_APP, "test", "me", 1);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1419,6 +1404,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
// Setup configs for user 10 and 11.
setupZenConfig();
ZenModeConfig config10 = mZenModeHelper.mConfig.copy();
@@ -1435,15 +1424,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
SYSTEM_UID);
// Backup user 10 and reset values.
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10, true, logger);
ZenModeConfig newConfig11 = new ZenModeConfig();
newConfig11.user = 11;
mZenModeHelper.mConfigs.put(11, newConfig11);
// Parse backup data.
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, 10);
- mZenModeHelper.readXml(parser, true, 11);
+ mZenModeHelper.readXml(parser, true, 10, logger);
+ parser = getParserForByteStream(baos);
+ mZenModeHelper.readXml(parser, true, 11, logger);
ZenModeConfig actual = mZenModeHelper.mConfigs.get(10);
if (Flags.modesUi()) {
@@ -1457,39 +1447,72 @@ public class ZenModeHelperTest extends UiServiceTestCase {
"Config mismatch: current vs expected: "
+ new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
assertNotEquals("Expected config mismatch", config11, mZenModeHelper.mConfigs.get(11));
+
+ if (android.app.Flags.backupRestoreLogging()) {
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, 1);
+ // If this is modes_ui, this is manual + single default rule
+ // If not modes_ui, it's two default automatic rules + manual policy
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, Flags.modesUi() ? 2 : 3);
+ verify(logger, never())
+ .logItemsBackupFailed(anyString(), anyInt(), anyString());
+
+ verify(logger, times(2)).logItemsRestored(DATA_TYPE_ZEN_RULES, Flags.modesUi() ? 2 : 3);
+ verify(logger, never())
+ .logItemsRestoreFailed(anyString(), anyInt(), anyString());
+ }
}
@Test
public void testReadXmlRestore_forSystemUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
setupZenConfig();
// one enabled automatic rule
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
ZenModeConfig original = mZenModeHelper.mConfig.copy();
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(
+ null, UserHandle.USER_SYSTEM, true, logger);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM, logger);
assertEquals("Config mismatch: current vs original: "
+ new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original),
original, mZenModeHelper.mConfig);
assertEquals(original.hashCode(), mZenModeHelper.mConfig.hashCode());
+
+ if (android.app.Flags.backupRestoreLogging()) {
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, 1);
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2);
+ verify(logger, never())
+ .logItemsBackupFailed(anyString(), anyInt(), anyString());
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_RULES, 2);
+ verify(logger, never())
+ .logItemsRestoreFailed(anyString(), anyInt(), anyString());
+ }
}
/** Restore should ignore the data's user id and restore for the target user. */
@Test
public void testReadXmlRestore_forNonSystemUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
// Setup config.
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
// Backup data for user 0.
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(
+ null, UserHandle.USER_SYSTEM, true, logger);
// Restore data for user 10.
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, 10);
+ mZenModeHelper.readXml(parser, true, 10, logger);
ZenModeConfig actual = mZenModeHelper.mConfigs.get(10);
expected.user = 10;
@@ -1503,17 +1526,21 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testReadXmlRestore_doesNotEnableManualRule() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
setupZenConfig();
// Turn on manual zen mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
ORIGIN_USER_IN_SYSTEMUI, "", "someCaller", SYSTEM_UID);
ZenModeConfig original = mZenModeHelper.mConfig.copy();
assertThat(original.isManualActive()).isTrue();
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL, logger);
ZenModeConfig result = mZenModeHelper.getConfig();
assertThat(result.isManualActive()).isFalse();
@@ -1567,7 +1594,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -1578,6 +1605,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testReadXmlRestoreWithZenPolicy_forSystemUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
final String ruleId = "customRule";
setupZenConfig();
@@ -1609,9 +1640,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
SystemZenRules.maybeUpgradeRules(mContext, expected);
}
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(
+ null, UserHandle.USER_SYSTEM, true, logger);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM, logger);
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -1623,7 +1655,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testReadXmlRulesNotOverridden() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// automatic zen rule is enabled on upgrade so rules should not be overriden to default
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
@@ -1643,10 +1675,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.automaticRules.containsKey("customRule"));
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
@@ -1663,7 +1695,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
@@ -1679,7 +1711,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
}
@@ -1698,7 +1730,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
}
@@ -1717,7 +1749,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.getZenPolicy()
.isVisualEffectAllowed(VISUAL_EFFECT_FULL_SCREEN_INTENT, true)).isFalse();
@@ -1742,7 +1774,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.getZenPolicy()
.isVisualEffectAllowed(VISUAL_EFFECT_PEEK, true)).isFalse();
@@ -1761,7 +1793,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.getZenPolicy()
.isVisualEffectAllowed(VISUAL_EFFECT_FULL_SCREEN_INTENT, true)).isFalse();
@@ -1776,7 +1808,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testReadXmlResetDefaultRules() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// no enabled automatic zen rules and no default rules
// so rules should be overridden by default rules
@@ -1788,7 +1820,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1797,13 +1829,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertTrue(rules.containsKey(defaultId));
}
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// all automatic zen rules are disabled on upgrade (and default rules don't already exist)
// so rules should be overriden by default rules
@@ -1824,7 +1856,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1834,14 +1866,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
assertFalse(rules.containsKey("customRule"));
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
@DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule
public void testReadXmlOnlyOneDefaultRuleExists() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// all automatic zen rules are disabled on upgrade and only one default rule exists
// so rules should be overriden to the default rules
@@ -1878,7 +1910,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1888,13 +1920,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
assertThat(rules).doesNotContainKey("customRule");
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
public void testReadXmlDefaultRulesExist() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// Default rules exist so rules should not be overridden by defaults
ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
@@ -1948,7 +1980,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default
@@ -1959,7 +1991,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
assertThat(rules).containsKey("customRule");
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1972,7 +2004,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// When reading XML for something that is already on the modes API system, make sure no
// rules' policies get changed.
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// Shared for rules
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRules = new ArrayMap<>();
@@ -2001,10 +2033,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// basic check: global config maintained
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
// Find our automatic rules.
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2021,7 +2053,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// a custom policy matching the global config for any automatic rule with no specified
// policy.
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
@@ -2040,10 +2072,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// basic check: global config maintained
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
// Find our automatic rule and check that it has a policy set now
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2073,7 +2105,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// underspecified ZenPolicy, we fill in all of the gaps with things from the global config
// in order to maintain consistency of behavior.
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
@@ -2097,10 +2129,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// basic check: global config maintained
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
// Find our automatic rule and check that it has a policy set now
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2163,7 +2195,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2215,7 +2247,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// Implicit rule was updated.
assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleBeforeModesUi.id))
@@ -2253,7 +2285,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// Both rules were untouched
assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleWithModesUi.id))
@@ -2394,8 +2426,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
// We need the package name to be something that's not "android" so there aren't any
// existing rules under that package.
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -2405,8 +2437,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -2426,8 +2458,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -2437,8 +2469,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -2458,8 +2490,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -2469,8 +2501,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -2485,8 +2517,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2506,8 +2538,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2532,8 +2564,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id1 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule1,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Zen rule with partially-filled policy: should get all of the filled fields set, and the
// rest filled with default state
@@ -2547,8 +2579,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.showFullScreenIntent(true)
.build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule2,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
// rule 1 should exist
assertThat(id1).isNotNull();
@@ -2593,9 +2625,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, zenRule.getConditionId(),
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
ORIGIN_APP,
CUSTOM_PKG_UID);
@@ -2613,8 +2645,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
null,
@@ -2623,7 +2655,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule2, ORIGIN_APP, "", CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule2, ORIGIN_APP, "",
+ CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertEquals("NEW", ruleInConfig.name);
@@ -2638,15 +2671,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id, ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2658,16 +2692,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRules(UserHandle.CURRENT, mContext.getPackageName(),
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2682,18 +2716,18 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule2,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
Condition condition = new Condition(sharedUri, "", STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition,
+ ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2707,8 +2741,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
condition = new Condition(sharedUri, "", STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition,
+ ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2738,14 +2772,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldMaximizeDoze(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -2771,14 +2806,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldMaximizeDoze(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@@ -2798,7 +2834,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldMaximizeDoze(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(zde)
@@ -2806,7 +2843,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_USER_IN_SYSTEMUI,
"reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@@ -2818,26 +2855,27 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldDisableTapToWake(true)
.addExtraEffect("extra")
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
.setShouldMaximizeDoze(true) // Bad
.addExtraEffect("should be rejected") // Bad
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(updateFromApp)
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // From update.
@@ -2852,24 +2890,25 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
.setShouldMaximizeDoze(true) // Also good
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromSystem)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
}
@@ -2879,12 +2918,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true)
@@ -2893,13 +2933,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// even with this line removed, tap to wake would be set to false.
.setShouldDisableTapToWake(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromUser)
.build(),
ORIGIN_USER_IN_SYSTEMUI, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
}
@@ -2909,7 +2949,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void updateAutomaticZenRule_nullPolicy_doesNothing() {
// Test that when updateAutomaticZenRule is called with a null policy, nothing changes
// about the existing policy.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setZenPolicy(new ZenPolicy.Builder()
@@ -2918,13 +2959,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build(),
ORIGIN_APP, "reasons", 0);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
// no zen policy
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
.isEqualTo(STATE_DISALLOW);
}
@@ -2935,7 +2976,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Test that when updating an automatic zen rule with an existing policy, the newly set
// fields overwrite those from the previous policy, but unset fields in the new policy
// keep values from the previous one.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setZenPolicy(new ZenPolicy.Builder()
@@ -2944,9 +2986,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowReminders(true)
.build())
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setZenPolicy(new ZenPolicy.Builder()
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
@@ -2954,7 +2996,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
.isEqualTo(STATE_ALLOW); // from update
assertThat(savedRule.getZenPolicy().getPriorityCallSenders())
@@ -2980,9 +3022,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg",
+ bedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
}
@@ -3001,9 +3042,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg",
+ bedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, bedtimeRuleId);
@@ -3023,30 +3063,56 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg",
+ bedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, bedtimeRuleId);
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
+ ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+ sleepingRule.enabled = false;
+ sleepingRule.userModifiedFields = 0;
+ sleepingRule.name = "ZZZZZZZ...";
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+ AutomaticZenRule futureBedtime = new AutomaticZenRule.Builder("Bedtime (?)", CONDITION_ID)
+ .build();
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg,
+ futureBedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+ .containsExactly(sleepingRule.id, bedtimeRuleId);
+
+ AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime (!)", CONDITION_ID)
+ .setType(TYPE_BEDTIME)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, bedtimeRuleId, bedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
+ }
+
+ @Test
@EnableFlags(FLAG_MODES_API)
public void testSetManualZenMode() {
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
- "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, Global.ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -3061,22 +3127,21 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
- ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, activeRuleId,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertWithMessage("Failure for origin " + origin.name())
.that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// User turns DND off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, origin.value(),
"snoozing", "systemui", SYSTEM_UID);
assertWithMessage("Failure for origin " + origin.name())
.that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -3100,21 +3165,20 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
- ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, activeRuleId,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// User turns DND off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, origin.value(),
"snoozing", "systemui", SYSTEM_UID);
ZenModeConfig config = mZenModeHelper.mConfig;
if (origin == ZenChangeOrigin.ORIGIN_USER_IN_SYSTEMUI) {
@@ -3145,15 +3209,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
- null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -3166,14 +3230,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
- null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "", null,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "",
+ null, CUSTOM_PKG_UID);
// In total, this should be 2 loggable changes
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -3242,22 +3305,21 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
// Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
// that look like they're coming from the system are attributed to the app, but when
// modes_ui is true, we opt to trust the provided change origin.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
- CUSTOM_PKG_UID);
+ Flags.modesUi() ? ORIGIN_APP : ORIGIN_SYSTEM, CUSTOM_PKG_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "",
SYSTEM_UID);
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -3266,18 +3328,19 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "test",
+ String systemId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), systemRule,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "test",
SYSTEM_UID);
// Event 3: turn on the system rule
- mZenModeHelper.setAutomaticZenRuleState(systemId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, systemId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
// Event 4: "User" deletes the rule
- mZenModeHelper.removeAutomaticZenRule(systemId,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, systemId,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "",
SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3333,7 +3396,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
- Flags.modesUi() ? ZenModeConfig.ORIGIN_SYSTEM : 0);
+ Flags.modesUi() ? ORIGIN_SYSTEM : 0);
// When the system rule is deleted, we consider this a user action that turns DND off
// (again)
@@ -3361,27 +3424,27 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on manually when the user turns it on in the app
// ("Turn on bedtime now" because user goes to bed earlier).
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
// Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends)
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts)
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 4: User deactivates the rule manually (they get up before 8 AM on the next day)
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
@@ -3449,21 +3512,23 @@ public class ZenModeHelperTest extends UiServiceTestCase {
setupZenConfig();
// First just turn zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
ORIGIN_USER_IN_SYSTEMUI, "", null, SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
- null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Change the policy again
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
@@ -3507,8 +3572,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Rule 2, same as rule 1
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3517,8 +3582,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Rule 3, has stricter settings than the default settings
ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -3529,28 +3594,27 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
ruleConfig.getZenPolicy(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id3 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule3, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// First: turn on rule 1
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// Second: turn on rule 2
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Third: turn on rule 3
- mZenModeHelper.setAutomaticZenRuleState(id3,
- new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id3,
+ new Condition(zenRule3.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Fourth: Turn *off* rule 2
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_FALSE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// This should result in a total of four events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3634,8 +3698,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", SYSTEM_UID);
// Rule 2, same as rule 1 but owned by the system
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3644,41 +3708,38 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID);
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
// re-evaluated to the one associated with CUSTOM_PKG_NAME.
// When modes_ui is true: we expect the change origin to be the source of truth.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
- SYSTEM_UID);
+ Flags.modesUi() ? ORIGIN_APP : ORIGIN_SYSTEM, SYSTEM_UID);
// Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
// (nor even looked up; the mock PackageManager won't handle "android" as input).
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Disable rule 1. Because this looks like a user action, the UID should not be modified
// from the system-provided one unless modes_ui is true.
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule,
ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ORIGIN_APP,
- "", null, CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP, "", null, CUSTOM_PKG_UID);
// Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
// the system, we keep the UID info.
// Note that this probably shouldn't be able to occur in real scenarios.
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- ORIGIN_APP, 12345);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_FALSE), ORIGIN_APP, 12345);
// That was 5 events total
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -3734,32 +3795,31 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Turn on zen mode with a manual rule with an enabler set. This should *not* count
// as a user action, and *should* get its UID reassigned.
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", CUSTOM_PKG_NAME, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", CUSTOM_PKG_NAME, SYSTEM_UID);
assertEquals(1, mZenModeEventLogger.numLoggedChanges());
// Now change apps bypassing to true
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.areChannelsBypassingDnd = true;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(),
+ ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
// and then back to false, all without changing anything else
newConfig.areChannelsBypassingDnd = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(),
+ ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
// Turn off manual mode, call from a package: don't reset UID even though enabler is set
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "",
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "",
CUSTOM_PKG_NAME, 12345);
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
// And likewise when turning it back on again
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ORIGIN_APP,
- "", CUSTOM_PKG_NAME, 12345);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP, "", CUSTOM_PKG_NAME, 12345);
// These are 5 events in total.
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -3804,8 +3864,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
setupZenConfig();
// First just turn zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Now change only the channels part of the policy; want to confirm that this'll be
// reflected in the logs
@@ -3815,8 +3875,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
oldPolicy.priorityMessageSenders, oldPolicy.suppressedVisualEffects,
STATE_PRIORITY_CHANNELS_BLOCKED,
oldPolicy.priorityConversationSenders);
- mZenModeHelper.setNotificationPolicy(newPolicy,
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newPolicy, ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Total events: one for turning on, one for changing policy
assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -3856,16 +3916,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Uri.parse("condition"),
null,
NotificationManager.INTERRUPTION_FILTER_ALL, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: App activates the rule automatically.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 2: App deactivates the rule automatically.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -3898,35 +3958,33 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, bedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
// Create immersive rule
AutomaticZenRule immersive = new AutomaticZenRule.Builder("Immersed", CONDITION_ID)
.setType(TYPE_IMMERSIVE)
.setZenPolicy(mZenModeHelper.mConfig.getZenPolicy()) // same as the manual rule
.build();
- String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String immersiveId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, immersive,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
// Event 1: Activate bedtime rule. This doesn't turn on notification filtering
- mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 2: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Event 3: Turn immersive on
- mZenModeHelper.setAutomaticZenRuleState(immersiveId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, immersiveId,
new Condition(immersive.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 4: Turn off bedtime mode, leaving just manual + immersive
- mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -3988,15 +4046,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
- assertEquals(mZenModeHelper.getNotificationPolicy(),
+ assertEquals(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT),
mZenModeHelper.getConsolidatedNotificationPolicy());
// inspect the consolidated policy. Based on setupZenConfig() values.
@@ -4024,13 +4082,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null, // null policy
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
// inspect the consolidated policy, which should match the device default settings.
assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
@@ -4062,13 +4120,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values (from device default or
@@ -4107,13 +4164,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values (from either device default
@@ -4147,13 +4203,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 1
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4171,13 +4226,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id2,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -4208,13 +4263,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 1
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4232,13 +4286,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -4274,13 +4328,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// confirm that channels make it through
assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4296,13 +4349,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
strictPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// rule 2 should override rule 1
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4327,9 +4380,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowSystem(true)
.build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRuleWithPriority, ORIGIN_APP, "test", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(rule1Id,
+ String rule1Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRuleWithPriority, ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, rule1Id,
new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -4340,9 +4393,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Uri.parse("priority"),
new ZenPolicy.Builder().disallowAllSounds().build(),
NotificationManager.INTERRUPTION_FILTER_ALL, true);
- String rule2Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRuleWithAll, ORIGIN_APP, "test", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(rule2Id,
+ String rule2Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRuleWithAll, ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, rule2Id,
new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -4386,7 +4439,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.triggerDescription = TRIGGER_DESC;
mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
- AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(rule.id);
+ AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, rule.id);
assertEquals(NAME, actual.getName());
assertEquals(OWNER, actual.getOwner());
@@ -4422,8 +4475,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setManualInvocationAllowed(ALLOW_MANUAL)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
- ORIGIN_APP, "add", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ OWNER.getPackageName(), azr, ORIGIN_APP, "add", CUSTOM_PKG_UID);
ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -4452,17 +4505,17 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Checks the name can be changed by the app because the user has not modified it.
AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("NewName");
// The user modifies some other field in the rule, which makes the rule as a whole not
@@ -4470,35 +4523,35 @@ public class ZenModeHelperTest extends UiServiceTestCase {
azrUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// ...but the app can still modify the name, because the name itself hasn't been modified
// by the user.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewAppName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("NewAppName");
// The user modifies the name.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("UserProvidedName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
// The app is no longer able to modify the name.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewAppName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
}
@@ -4512,9 +4565,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setDeviceEffects(new ZenDeviceEffects.Builder().build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Modifies the filter, icon, zen policy, and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
@@ -4532,9 +4585,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Update the rule with the AZR from origin user.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
@@ -4569,9 +4622,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Modifies the icon, zen policy and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
@@ -4589,9 +4642,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Update the rule with the AZR from origin systemUI.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_SYSTEM,
"reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
assertThat(rule.getIconResId()).isEqualTo(ICON_RES_ID);
@@ -4619,9 +4672,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
ZenPolicy policy = new ZenPolicy.Builder()
.allowReminders(true)
@@ -4637,8 +4690,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
// The bitmask is not modified.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.userModifiedFields).isEqualTo(0);
@@ -4652,8 +4705,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
// Creates another rule, this time from user. This will have user modified bits set.
- String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ String ruleIdUser = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
int ruleModifiedFields = storedRule.userModifiedFields;
int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
@@ -4661,9 +4714,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Zen rule update coming from the app again. This cannot fully update the rule, because
// the rule is already considered user modified.
- mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, ORIGIN_APP,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleIdUser, azrUpdate, ORIGIN_APP,
"reason", SYSTEM_UID);
- AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+ AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ ruleIdUser);
// The app can only change the value if the rule is not already user modified,
// so the rule is not changed, and neither is the bitmask.
@@ -4692,9 +4746,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldDisplayGrayscale(true)
.build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// The values are modified but the bitmask is not.
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
@@ -4714,8 +4768,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setDeviceEffects(zde)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Sets Device Effects to null
@@ -4724,9 +4778,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason",
SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept.
assertThat(rule.getDeviceEffects()).isEqualTo(zde);
@@ -4740,9 +4794,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setZenPolicy(POLICY)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Set zen policy to null
@@ -4751,9 +4805,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason",
SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
// (equivalent to the provided policy, with additional fields filled in with defaults).
@@ -4772,9 +4826,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// .setDeviceEffects(new ZenDeviceEffects.Builder().build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Create a fully populated ZenPolicy.
ZenPolicy policy = new ZenPolicy.Builder()
@@ -4802,9 +4856,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Applies the update to the rule.
// Default config defined in getDefaultConfigParser() is used as the original rule.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
@@ -4833,9 +4887,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setDeviceEffects(null)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -4845,9 +4899,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Applies the update to the rule.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// New ZenDeviceEffects is used; all fields considered set, since previously were null.
assertThat(rule.getDeviceEffects()).isNotNull();
@@ -4870,8 +4924,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4887,7 +4941,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, createdId, zenRule, ORIGIN_SYSTEM,
"", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
@@ -4905,8 +4959,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4922,7 +4976,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(true);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, createdId, zenRule, ORIGIN_SYSTEM,
"", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
@@ -4941,8 +4995,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4957,9 +5011,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
};
mZenModeHelper.addCallback(callback);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4981,8 +5035,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -4999,12 +5053,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
};
mZenModeHelper.addCallback(callback);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
- null, "", SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, Global.ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, null, "", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -5026,8 +5079,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -5044,13 +5097,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
};
mZenModeHelper.addCallback(callback);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_FALSE),
- ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE), ORIGIN_APP,
+ CUSTOM_PKG_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -5071,21 +5123,20 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// Event 2: Snooze rule by turning off DND
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
- "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, Global.ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Event 3: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, createdId, zenRule, ORIGIN_SYSTEM,
"", SYSTEM_UID);
assertEquals(OVERRIDE_NONE,
@@ -5100,17 +5151,17 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
.setTriggerDescription("Whenever")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateWithDiff,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
@@ -5124,15 +5175,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateUnchanged,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5147,17 +5198,17 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
.setTriggerDescription("Whenever")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateWithDiff,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5175,17 +5226,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId =
- mZenModeHelper.addAutomaticZenRule(
- mPkg, rule, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(
- ruleId, CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff =
new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateWithDiff,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5200,16 +5250,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder(rule).setEnabled(false).build(),
ORIGIN_USER_IN_SYSTEMUI, "disable", SYSTEM_UID);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder(rule).setEnabled(true).build(),
ORIGIN_USER_IN_SYSTEMUI, "enable", SYSTEM_UID);
@@ -5225,16 +5275,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName("android", "some.old.cps"))
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule("android", original,
- ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", original,
+ ORIGIN_SYSTEM, "reason", SYSTEM_UID);
AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName("android", "brand.new.cps"))
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(result).isNotNull();
assertThat(result.getOwner().getClassName()).isEqualTo("brand.new.cps");
}
@@ -5245,16 +5295,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps"))
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), original,
- ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), original, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mContext.getPackageName(), "new.third.party.cps"))
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(result).isNotNull();
assertThat(result.getOwner().getClassName()).isEqualTo("old.third.party.cps");
}
@@ -5269,14 +5319,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
.build());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(any(), eq(ORIGIN_APP));
// Now delete the (currently active!) rule. For example, assume this is done from settings.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "remove",
- SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_USER_IN_SYSTEMUI,
+ "remove", SYSTEM_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_USER_IN_SYSTEMUI));
@@ -5295,11 +5345,27 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = addRuleWithEffects(effects);
verifyNoMoreInteractions(mDeviceEffectsApplier);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(effects), eq(ORIGIN_APP));
+ assertTrue(mZenModeHelper.hasDeviceEffectsApplier());
+ }
+
+ @Test
+ public void testHasDeviceEffectsApplier_returnsFalseIfNotSet() {
+ assertFalse(mZenModeHelper.hasDeviceEffectsApplier());
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_API)
+ public void testSettingDeviceEffects_throwsExceptionIfAlreadySet() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier));
}
@Test
@@ -5309,13 +5375,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_FALSE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_APP));
@@ -5332,8 +5398,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldDisplayGrayscale(true)
.addExtraEffect("ONE")
.build());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(
eq(new ZenDeviceEffects.Builder()
@@ -5348,8 +5414,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldDimWallpaper(true)
.addExtraEffect("TWO")
.build());
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, secondRuleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(
@@ -5372,15 +5438,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.addExtraEffect("extra_effect")
.build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
// Now create and activate a second rule that doesn't add any more effects.
String secondRuleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, secondRuleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verifyNoMoreInteractions(mDeviceEffectsApplier);
@@ -5393,8 +5459,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = addRuleWithEffects(zde);
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
@@ -5434,8 +5500,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setDeviceEffects(effects)
.build();
- return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
+ return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
+ rule, ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
}
@Test
@@ -5448,35 +5514,37 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// App adds it again.
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was restored:
// - id and creation time is the same as the original one.
// - ZenPolicy is the one that the user had set.
// - rule still has the user-modified fields.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000.
assertThat(newRuleId).isEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
@@ -5503,24 +5571,26 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
// App adds it again.
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was recreated. This means id and creation time are new.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(3000);
assertThat(newRuleId).isNotEqualTo(ruleId);
}
@@ -5535,9 +5605,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// User customizes it.
mTestClock.advanceByMillis(1000);
@@ -5545,24 +5616,26 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// User creates it again (unusual case, but ok).
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_USER_IN_SYSTEMUI, "add it anew", SYSTEM_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_USER_IN_SYSTEMUI, "add it anew",
+ SYSTEM_UID);
// Verify that the rule was recreated. This means id and creation time are new, and the rule
// matches the latest data supplied to addAZR.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(4000);
assertThat(newRuleId).isNotEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
@@ -5583,9 +5656,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// User customizes it.
mTestClock.advanceByMillis(1000);
@@ -5593,23 +5667,24 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// User deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "delete it",
- SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_USER_IN_SYSTEMUI,
+ "delete it", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
// App creates it again.
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was recreated. This means id and creation time are new.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(4000);
assertThat(newRuleId).isNotEqualTo(ruleId);
}
@@ -5623,18 +5698,18 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(new ComponentName("first", "owner"))
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// App deletes it. It's preserved for a possible restoration.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5644,12 +5719,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(new ComponentName("second", "owner"))
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- readdingWithDifferentOwner, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), readdingWithDifferentOwner, ORIGIN_APP, "add it again",
+ CUSTOM_PKG_UID);
// Verify that the rule was NOT restored:
assertThat(newRuleId).isNotEqualTo(ruleId);
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner"));
@@ -5665,23 +5742,23 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.automaticRules.clear();
// Start with a bunch of customized rules where conditionUris are not unique.
- String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ String id1 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg1",
new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg1",
new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ String id3 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg1",
new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
+ String id4 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg2",
new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
+ String id5 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg2",
new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
@@ -5689,11 +5766,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- mZenModeHelper.removeAutomaticZenRule(id1, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id2, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id3, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id4, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id5, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id1, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id2, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id3, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id4, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id5, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
.containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
@@ -5707,11 +5789,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void removeAllZenRules_preservedForRestoring() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
@@ -5720,8 +5802,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP,
- "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRules(UserHandle.CURRENT, mContext.getPackageName(),
+ ORIGIN_APP, "begone", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
}
@@ -5738,8 +5820,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule);
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
- mZenModeHelper.removeAutomaticZenRules("pkg1",
- ZenModeConfig.ORIGIN_SYSTEM, "goodbye pkg1", SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRules(UserHandle.CURRENT, "pkg1",
+ ORIGIN_SYSTEM, "goodbye pkg1", SYSTEM_UID);
// Preserved rules from pkg1 are gone; those from pkg2 are still there.
assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
@@ -5755,36 +5837,37 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConditionId(CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App activates it.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// App deletes it.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App adds it again.
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
// ... but it is NOT active
@@ -5804,40 +5887,41 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setConditionId(CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App activates it.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// User snoozes it.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_SYSTEM,
"snoozing", "systemui", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App deletes it.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// App adds it again.
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
// ... but it is NEITHER active NOR snoozed.
@@ -5910,7 +5994,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_MODES_API)
public void getAutomaticZenRuleState_ownedRule_returnsRuleState() {
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setConfigurationActivity(
new ComponentName(mContext.getPackageName(), "Blah"))
@@ -5918,18 +6003,23 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
// Null condition -> STATE_FALSE
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_FALSE, ORIGIN_APP,
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_FALSE);
- mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_UNKNOWN);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id, ORIGIN_APP, "",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_UNKNOWN);
}
@Test
@@ -5944,8 +6034,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
- assertThat(mZenModeHelper.getAutomaticZenRuleState("systemRule")).isEqualTo(
- Condition.STATE_UNKNOWN);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, "systemRule"))
+ .isEqualTo(Condition.STATE_UNKNOWN);
}
@Test
@@ -5962,7 +6052,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
- mZenModeHelper.setAutomaticZenRuleState("otherRule",
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, "otherRule",
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -5983,7 +6073,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
- mZenModeHelper.setAutomaticZenRuleState(otherRule.conditionId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, otherRule.conditionId,
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -5994,7 +6084,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@EnableFlags(FLAG_MODES_API)
public void testCallbacks_policy() throws Exception {
setupZenConfig();
- assertThat(mZenModeHelper.getNotificationPolicy().allowReminders()).isTrue();
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowReminders())
+ .isTrue();
SettableFuture<Policy> futurePolicy = SettableFuture.create();
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
@@ -6004,7 +6095,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
});
Policy totalSilencePolicy = new Policy(0, 0, 0);
- mZenModeHelper.setNotificationPolicy(totalSilencePolicy, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, totalSilencePolicy, ORIGIN_APP,
+ CUSTOM_PKG_UID);
Policy callbackPolicy = futurePolicy.get(1, TimeUnit.SECONDS);
assertThat(callbackPolicy.allowReminders()).isFalse();
@@ -6022,13 +6114,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
});
- String totalSilenceRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String totalSilenceRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.build(),
ORIGIN_APP, "reasons", 0);
- mZenModeHelper.setAutomaticZenRuleState(totalSilenceRuleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, totalSilenceRuleId,
new Condition(CONDITION_ID, "", STATE_TRUE), ORIGIN_APP, CUSTOM_PKG_UID);
Policy callbackPolicy = futureConsolidatedPolicy.get(1, TimeUnit.SECONDS);
@@ -6040,8 +6133,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_METADATA)
@@ -6056,13 +6149,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "test",
+ "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_ALARMS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_METADATA)
@@ -6079,22 +6173,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String pkg = mContext.getPackageName();
// From app, call "setInterruptionFilter" and create and implicit rule.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
.isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// From user, update that rule's interruption filter.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setInterruptionFilter" again.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_NO_INTERRUPTIONS);
// The app's update was ignored, and the user's update is still current, and the current
@@ -6111,22 +6205,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String pkg = mContext.getPackageName();
// From app, call "setInterruptionFilter" and create and implicit rule.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
.isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// From user, update something in that rule, but not the interruption filter.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Renamed")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setInterruptionFilter" again.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_NO_INTERRUPTIONS);
// The app's update was accepted, and the current mode is the one that they wanted.
@@ -6139,13 +6233,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, mPkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
.isEqualTo(STATE_TRUE);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, mPkg, CUSTOM_PKG_UID,
ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
@@ -6157,8 +6251,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_OFF);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -6168,18 +6262,19 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
.isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "test",
+ "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
.isEqualTo(OVERRIDE_DEACTIVATE);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_ALARMS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
.isEqualTo(OVERRIDE_NONE);
@@ -6188,14 +6283,57 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalZenModeAsImplicitZenRule_again_refreshesRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ // "Break" the rule name to check that applying again restores it.
+ mZenModeHelper.mConfig.automaticRules.valueAt(0).name = "BOOM!";
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalZenModeAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName());
+
+ // User chooses a new name.
+ AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ new AutomaticZenRule.Builder(azr).setName("User chose this").build(),
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+
+ // App triggers the rule again.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("User chose this");
+ }
+
+ @Test
@DisableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
- () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -6208,7 +6346,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, policy);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -6232,14 +6371,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Policy original = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- original);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, original);
// Change priorityCallSenders: contacts -> starred.
Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, updated);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -6263,7 +6403,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// From app, call "setNotificationPolicy" and create and implicit rule.
Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ originalPolicy);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
// Store this for checking later.
@@ -6271,18 +6412,19 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update that rule's policy.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
.allowAlarms(true).build();
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setZenPolicy(userUpdateZenPolicy)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setNotificationPolicy" again.
Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ appUpdatePolicy);
// The app's update was ignored, and the user's update is still current.
assertThat(mZenModeHelper.mConfig.automaticRules.values())
@@ -6303,7 +6445,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// From app, call "setNotificationPolicy" and create and implicit rule.
Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ originalPolicy);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
// Store this for checking later.
@@ -6311,16 +6454,17 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update something in that rule, but not the ZenPolicy.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Rule renamed, not touching policy")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setNotificationPolicy" again.
Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ appUpdatePolicy);
// The app's update was applied.
ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
@@ -6333,13 +6477,57 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalPolicyAsImplicitZenRule_again_refreshesRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ // "Break" the rule name to check that updating it again restores it.
+ mZenModeHelper.mConfig.automaticRules.valueAt(0).name = "BOOM!";
+
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalPolicyAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName());
+
+ // User chooses a new name.
+ AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ new AutomaticZenRule.Builder(azr).setName("User chose this").build(),
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+
+ // App updates the implicit rule again.
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("User chose this");
+ }
+
+ @Test
@DisableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
- () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0)));
+ () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ CUSTOM_PKG_NAME, CUSTOM_PKG_UID, new Policy(0, 0, 0)));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -6351,11 +6539,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, writtenPolicy);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
- CUSTOM_PKG_NAME);
+ UserHandle.CURRENT, CUSTOM_PKG_NAME);
assertThat(readPolicy).isEqualTo(writtenPolicy);
}
@@ -6365,15 +6553,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@DisableFlags(FLAG_MODES_UI)
public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() {
// Implicit rule will get the global policy at the time of rule creation.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// If the policy then changes afterwards, it should inherit updates because user cannot
// edit the policy in the UI.
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
- ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0), ORIGIN_APP, 1);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
- CUSTOM_PKG_NAME);
+ UserHandle.CURRENT, CUSTOM_PKG_NAME);
assertThat(readPolicy).isNotNull();
assertThat(readPolicy.allowCalls()).isFalse();
@@ -6384,10 +6572,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0);
- mZenModeHelper.setNotificationPolicy(policy, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, policy, ORIGIN_APP,
+ CUSTOM_PKG_UID);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
- CUSTOM_PKG_NAME);
+ UserHandle.CURRENT, CUSTOM_PKG_NAME);
assertThat(readPolicy).isNotNull();
assertThat(readPolicy.allowCalls()).isTrue();
@@ -6420,7 +6609,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
- mZenModeHelper.setNotificationPolicy(newManualPolicy, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newManualPolicy, ORIGIN_APP,
+ CUSTOM_PKG_UID);
ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
// Only app rules with default or same-as-manual policies were updated.
@@ -6448,10 +6638,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(mResources.getResourceName(resourceId)).thenReturn(veryLongResourceName);
when(mResources.getIdentifier(veryLongResourceName, null, null)).thenReturn(resourceId);
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(),
ORIGIN_APP, "reason", CUSTOM_PKG_UID);
- AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ ruleId);
assertThat(storedRule.getIconResId()).isEqualTo(0);
}
@@ -6462,8 +6654,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
- SYSTEM_UID);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT, effects,
+ ORIGIN_USER_IN_SYSTEMUI, "settings", SYSTEM_UID);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6473,14 +6665,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
@EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
public void setManualZenRuleDeviceEffects_preexistingMode() {
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, ORIGIN_USER_IN_SYSTEMUI,
- "create manual rule", "settings", SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
+ ORIGIN_USER_IN_SYSTEMUI, "create manual rule", "settings", SYSTEM_UID);
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
- SYSTEM_UID);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT, effects,
+ ORIGIN_USER_IN_SYSTEMUI, "settings", SYSTEM_UID);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6495,7 +6687,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setEnabled(false)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsDisabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
@@ -6510,7 +6702,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsEnabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
@@ -6519,8 +6711,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowDisabled,
+ ORIGIN_USER_IN_SYSTEMUI, "off", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
ORIGIN_USER_IN_SYSTEMUI);
@@ -6533,14 +6725,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsEnabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowDisabled,
+ ORIGIN_USER_IN_SYSTEMUI, "off", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
ORIGIN_USER_IN_SYSTEMUI);
@@ -6548,8 +6740,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule nowRenamed = new AutomaticZenRule.Builder(nowDisabled)
.setName("Fancy pants rule")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, ORIGIN_APP, "update",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowRenamed, ORIGIN_APP,
+ "update", CUSTOM_PKG_UID);
// Identity of the disabler is preserved.
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
@@ -6563,14 +6755,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsEnabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowDisabled,
+ ORIGIN_USER_IN_SYSTEMUI, "off", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
ORIGIN_USER_IN_SYSTEMUI);
@@ -6578,8 +6770,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule nowEnabled = new AutomaticZenRule.Builder(nowDisabled)
.setEnabled(true)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, ORIGIN_APP, "on",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowEnabled, ORIGIN_APP,
+ "on", CUSTOM_PKG_UID);
// Identity of the disabler was cleared.
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
@@ -6592,10 +6784,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
@@ -6610,13 +6802,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE,
SOURCE_CONTEXT);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6624,7 +6816,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6633,7 +6825,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(zenRule.condition).isNull();
// Bonus check: app has resumed control over the rule and can now turn it on.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, autoOn, ORIGIN_APP,
+ CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
@@ -6646,21 +6839,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE,
SOURCE_CONTEXT);
Condition autoOff = new Condition(rule.getConditionId(), "auto-off", STATE_FALSE,
SOURCE_CONTEXT);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, autoOn, ORIGIN_APP,
+ CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
assertThat(zenRule.condition).isEqualTo(autoOn);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6668,7 +6862,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
assertThat(zenRule.condition).isEqualTo(autoOn);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6677,7 +6871,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(zenRule.condition).isEqualTo(autoOn);
// Bonus check: app has resumed control over the rule and can now turn it off.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, autoOff, ORIGIN_APP,
+ CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
@@ -6690,10 +6885,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6701,7 +6896,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
assertThat(zenRuleOn.condition).isNotNull();
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
ZenRule zenRuleOff = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6716,31 +6911,31 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6753,39 +6948,39 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6799,18 +6994,18 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
// User manually turns on rule from SysUI / Settings...
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on-from-sysui", STATE_TRUE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
// ... and they can turn it off manually from inside the app.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off-from-app", STATE_FALSE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isFalse();
@@ -6823,25 +7018,25 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
// Rule is activated due to its schedule.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on-from-app", STATE_TRUE,
SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
// User manually turns off rule from SysUI / Settings...
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off-from-sysui", STATE_FALSE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
assertThat(getZenRule(ruleId).isActive()).isFalse();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
// ... and they can turn it on manually from inside the app.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
@@ -6854,25 +7049,69 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
// Rule is manually activated by the user in the app.
// This turns the rule on, but is NOT an override...
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
// ... so the app can turn it off when its schedule is over.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off-from-app", STATE_FALSE,
SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isFalse();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_implicitRuleManualActivation_doesNotUseOverride() {
+ mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
+ PERMISSION_GRANTED); // So that canManageAZR succeeds.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_OFF);
+ ZenRule implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
+ assertThat(implicitRule.isActive()).isFalse();
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id,
+ new Condition(implicitRule.conditionId, "on!", STATE_TRUE, SOURCE_USER_ACTION),
+ ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+
+ implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id))
+ .isEqualTo(STATE_TRUE);
+ assertThat(implicitRule.isActive()).isTrue();
+ assertThat(implicitRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_implicitRuleManualDeactivation_doesNotUseOverride() {
+ mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
+ PERMISSION_GRANTED); // So that canManageAZR succeeds.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ ZenRule implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
+ assertThat(implicitRule.isActive()).isTrue();
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id,
+ new Condition(implicitRule.conditionId, "off!", STATE_FALSE, SOURCE_USER_ACTION),
+ ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+
+ implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id))
+ .isEqualTo(STATE_FALSE);
+ assertThat(implicitRule.isActive()).isFalse();
+ assertThat(implicitRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+ }
+
private ZenRule getZenRule(String ruleId) {
return checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId),
"Didn't find rule with id %s", ruleId);
@@ -6931,12 +7170,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_TRUE);
ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
@@ -6947,15 +7187,17 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Now simulate a reboot -> reload the configuration after purging.
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
if (Flags.modesUi()) {
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_TRUE);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
} else {
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_FALSE);
}
}
@@ -6966,15 +7208,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "snooze", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_FALSE);
ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
assertThat(zenRule.condition).isNotNull();
@@ -6985,9 +7228,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Now simulate a reboot -> reload the configuration after purging.
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_TRUE);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
assertThat(zenRule.condition).isNotNull();
@@ -7000,8 +7244,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, azr, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
ZenRule zenRule = checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId));
@@ -7023,7 +7267,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey(
ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
@@ -7043,12 +7287,96 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.automaticRules).containsKey(
ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void updateHasPriorityChannels_keepsChannelSettings() {
+ setupZenConfig();
+
+ // Set priority channels setting on manual mode to confirm that it is unaffected by changes
+ // to the state describing the existence of such channels.
+ mZenModeHelper.mConfig.manualRule.zenPolicy =
+ new ZenPolicy.Builder(mZenModeHelper.mConfig.manualRule.zenPolicy)
+ .allowPriorityChannels(false)
+ .build();
+
+ mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT, true);
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).hasPriorityChannels())
+ .isTrue();
+
+ // getNotificationPolicy() gets its policy from the manual rule; channels not permitted
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowPriorityChannels())
+ .isFalse();
+
+ mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT, false);
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).hasPriorityChannels())
+ .isFalse();
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowPriorityChannels())
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_MULTIUSER)
+ public void setManualZenMode_fromCurrentUser_updatesCurrentConfig() {
+ // Initialize default configurations (default rules) for both users.
+ mZenModeHelper.onUserSwitched(1);
+ mZenModeHelper.onUserSwitched(2);
+ UserHandle currentUser = UserHandle.of(2);
+ ZenModeHelper.Callback callback = mock(ZenModeHelper.Callback.class);
+ mZenModeHelper.addCallback(callback);
+
+ mZenModeHelper.setManualZenMode(currentUser, ZEN_MODE_ALARMS, null, ORIGIN_APP, "reason",
+ mPkg, CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.isManualActive()).isTrue();
+ assertThat(mZenModeHelper.mConfigs.get(1).isManualActive()).isFalse();
+
+ // And we sent the broadcast announcing the change.
+ mTestableLooper.processAllMessages();
+ verify(callback).onZenModeChanged();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_MULTIUSER)
+ public void setInterruptionFilter_fromNonCurrentUser_updatesNonCurrentConfig() {
+ // Initialize default configurations (default rules) for both users.
+ // Afterwards, 2 is current, and 1 is background.
+ mZenModeHelper.onUserSwitched(1);
+ mZenModeHelper.onUserSwitched(2);
+ UserHandle backgroundUser = UserHandle.of(1);
+ ZenModeHelper.Callback callback = mock(ZenModeHelper.Callback.class);
+ mZenModeHelper.addCallback(callback);
+
+ mZenModeHelper.setManualZenMode(backgroundUser, ZEN_MODE_ALARMS, null, ORIGIN_APP, "reason",
+ mPkg, CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.isManualActive()).isFalse();
+ assertThat(mZenModeHelper.mConfigs.get(1).isManualActive()).isTrue();
+
+ // And no broadcasts is sent for "background" changes (they were not evaluated).
+ mTestableLooper.processAllMessages();
+ verify(callback, never()).onZenModeChanged();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_MULTIUSER)
+ public void getNotificationPolicy_fromUserWithoutZenConfig_returnsDefaultPolicy() {
+ // Set a custom policy for the current user to double check we return a default one below.
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, new Policy(0, 0, 0), ORIGIN_SYSTEM,
+ SYSTEM_UID);
+
+ Policy ghostPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.of(5552368));
+
+ assertThat(ghostPolicy).isNotNull();
+ assertThat(ZenAdapters.notificationPolicyToZenPolicy(ghostPolicy))
+ .isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
@@ -7099,9 +7427,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.zenMode = zenMode;
rule.zenPolicy = policy;
rule.pkg = ownerPkg;
- rule.name = CUSTOM_APP_LABEL;
- if (!Flags.modesUi()) {
- rule.iconResName = ICON_RES_NAME;
+ if (Flags.modesUi()) {
+ rule.name = mContext.getString(R.string.zen_mode_implicit_name, CUSTOM_APP_LABEL);
+ } else {
+ rule.name = CUSTOM_APP_LABEL;
}
rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
CUSTOM_APP_LABEL);
@@ -7121,7 +7450,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
SUPPRESSED_EFFECT_BADGE,
0,
CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.setNotificationPolicy(customPolicy, ORIGIN_UNKNOWN, 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, customPolicy, ORIGIN_UNKNOWN, 1);
if (!Flags.modesUi()) {
mZenModeHelper.mConfig.manualRule = null;
}
diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml
index c0f514fb9673..850884f84b01 100644
--- a/services/tests/vibrator/AndroidManifest.xml
+++ b/services/tests/vibrator/AndroidManifest.xml
@@ -32,6 +32,9 @@
<uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" />
<!-- Required to play system-only haptic feedback constants -->
<uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" />
+ <!-- Required to play vendor effects and start vendor sessions -->
+ <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" />
+ <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" />
<application android:debuggable="true">
<uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 1493253a50d4..d5548a4f375e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -16,6 +16,16 @@
package com.android.server.vibrator;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_FALL;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SLOW_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
@@ -29,13 +39,18 @@ import android.os.Handler;
import android.os.PersistableBundle;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.os.vibrator.BasicPwleSegment;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwleSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import androidx.test.core.app.ApplicationProvider;
@@ -55,14 +70,32 @@ public class DeviceAdapterTest {
private static final int EMPTY_VIBRATOR_ID = 1;
private static final int PWLE_VIBRATOR_ID = 2;
private static final int PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID = 3;
+ private static final int PWLE_V2_VIBRATOR_ID = 4;
+ private static final int BASIC_VIBRATOR_ID = 5;
private static final float TEST_MIN_FREQUENCY = 50;
private static final float TEST_RESONANT_FREQUENCY = 150;
private static final float TEST_FREQUENCY_RESOLUTION = 25;
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.08f, 0.16f, 0.32f, 0.64f, /* 150Hz= */ 0.8f, 0.72f, /* 200Hz= */ 0.64f};
+ private static final int TEST_MAX_ENVELOPE_EFFECT_SIZE = 10;
+ private static final int TEST_MIN_ENVELOPE_EFFECT_CONTROL_POINT_DURATION_MILLIS = 20;
+ private static final float[] TEST_FREQUENCIES_HZ = new float[]{30f, 50f, 100f, 120f, 150f};
+ private static final float[] TEST_OUTPUT_ACCELERATIONS_GS =
+ new float[]{0.0f, 3.0f, 4.0f, 2.0f, 1.0f};
+
+ private static final float[] TEST_BASIC_FREQUENCIES_HZ = new float[]{50f, 200f, 400f, 500f};
+ private static final float[] TEST_BASIC_OUTPUT_ACCELERATIONS_GS =
+ new float[]{0.05f, 0.5f, 2.0f, 1.0f};
+
+ private static final float PWLE_V2_MIN_FREQUENCY = TEST_FREQUENCIES_HZ[0];
+ private static final float PWLE_V2_MAX_FREQUENCY =
+ TEST_FREQUENCIES_HZ[TEST_FREQUENCIES_HZ.length - 1];
+ private static final int TEST_PRIMITIVE_DURATION = 20;
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManagerInternal mPackageManagerInternalMock;
@@ -89,6 +122,7 @@ public class DeviceAdapterTest {
vibrators.put(PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID,
createPwleWithoutFrequenciesVibratorController(
PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID));
+ vibrators.put(BASIC_VIBRATOR_ID, createBasicVibratorController(BASIC_VIBRATOR_ID));
mAdapter = new DeviceAdapter(mVibrationSettings, vibrators);
}
@@ -103,12 +137,12 @@ public class DeviceAdapterTest {
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
/* repeatIndex= */ -1);
- assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isEqualTo(effect);
+ assertThat(mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect)).isEqualTo(effect);
assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(effect);
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void testVendorEffect_returnsOriginalSegment() {
PersistableBundle vendorData = new PersistableBundle();
vendorData.putInt("key", 1);
@@ -186,6 +220,7 @@ public class DeviceAdapterTest {
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValuesOnlyInRamps() {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
// Individual step without frequency control, will not use PWLE composition
@@ -220,10 +255,10 @@ public class DeviceAdapterTest {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
- new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+ new StepSegment(1, 0, 10),
new PrebakedSegment(
VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
- new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
+ new StepSegment(1, 0, 10)),
/* repeatIndex= */ -1);
CombinedVibration expected = CombinedVibration.createParallel(effect);
@@ -246,6 +281,11 @@ public class DeviceAdapterTest {
new StepSegment(1, 175, 10),
new StepSegment(1, 0, 50)),
/* repeatIndex= */ 1))
+ .addVibrator(BASIC_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
+ // Step(amplitude, frequencyHz, duration)
+ new StepSegment(1, 175, 10),
+ new StepSegment(1, 0, 50)),
+ /* repeatIndex= */ 1))
.addVibrator(PWLE_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
// Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
new RampSegment(0.72f, 0.72f, 175, 175, 10),
@@ -291,26 +331,224 @@ public class DeviceAdapterTest {
assertThat(vibration.adapt(mAdapter)).isEqualTo(expected);
}
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testPwleSegment_withoutPwleV2Capability_returnsNull() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100),
+ new PwleSegment(1, 0.2f, 30, 60, 20),
+ new PwleSegment(0.8f, 0.2f, 60, 100, 100),
+ new PwleSegment(0.65f, 0.65f, 100, 50, 50)),
+ /* repeatIndex= */ 1);
+
+ VibrationEffect.Composed adaptedEffect =
+ (VibrationEffect.Composed) mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect);
+ assertThat(adaptedEffect).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testPwleSegment_withPwleV2Capability_returnsAdaptedSegments() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PwleSegment(1, 0.2f, 30, 60, 20),
+ new PwleSegment(0.8f, 0.2f, 60, 100, 100),
+ new PwleSegment(0.65f, 0.65f, 100, 50, 50)),
+ /* repeatIndex= */ 1);
+
+ VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+ new PwleSegment(1, 0.2f, 30, 60, 20),
+ new PwleSegment(0.8f, 0.2f, 60, 100, 100),
+ new PwleSegment(0.65f, 0.65f, 100, 50, 50)),
+ /* repeatIndex= */ 1);
+
+ SparseArray<VibratorController> vibrators = new SparseArray<>();
+ vibrators.put(PWLE_V2_VIBRATOR_ID, createPwleV2VibratorController(PWLE_V2_VIBRATOR_ID));
+ DeviceAdapter adapter = new DeviceAdapter(mVibrationSettings, vibrators);
+
+ assertThat(adapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isEqualTo(expected);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testPwleSegment_withFrequenciesBelowSupportedRange_returnsNull() {
+ float frequencyBelowSupportedRange = PWLE_V2_MIN_FREQUENCY - 1f;
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PwleSegment(0, 0.2f, 30, 60, 20),
+ new PwleSegment(0.8f, 0.2f, 60, frequencyBelowSupportedRange, 100),
+ new PwleSegment(0.65f, 0.65f, frequencyBelowSupportedRange, 50, 50)),
+ /* repeatIndex= */ 1);
+
+ SparseArray<VibratorController> vibrators = new SparseArray<>();
+ vibrators.put(PWLE_V2_VIBRATOR_ID, createPwleV2VibratorController(PWLE_V2_VIBRATOR_ID));
+ DeviceAdapter adapter = new DeviceAdapter(mVibrationSettings, vibrators);
+
+ assertThat(adapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testPwleSegment_withFrequenciesAboveSupportedRange_returnsNull() {
+ float frequencyAboveSupportedRange = PWLE_V2_MAX_FREQUENCY + 1f;
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PwleSegment(0, 0.2f, 30, frequencyAboveSupportedRange, 20),
+ new PwleSegment(0.8f, 0.2f, frequencyAboveSupportedRange, 100, 100),
+ new PwleSegment(0.65f, 0.65f, 100, 50, 50)),
+ /* repeatIndex= */ 1);
+
+ SparseArray<VibratorController> vibrators = new SparseArray<>();
+ vibrators.put(PWLE_V2_VIBRATOR_ID, createPwleV2VibratorController(PWLE_V2_VIBRATOR_ID));
+ DeviceAdapter adapter = new DeviceAdapter(mVibrationSettings, vibrators);
+
+ assertThat(adapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicPwleSegment_withoutPwleV2Capability_returnsNull() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100),
+ new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+ new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+ new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)),
+ /* repeatIndex= */ 1);
+
+ VibrationEffect.Composed adaptedEffect =
+ (VibrationEffect.Composed) mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect);
+ assertThat(adaptedEffect).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicPwleSegment_withPwleV2Capability_returnsAdaptedSegments() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new BasicPwleSegment(0.0f, 0.5f, 0.0f, 0.5f, 20),
+ new BasicPwleSegment(0.5f, 1.0f, 0.5f, 1.0f, 100),
+ new BasicPwleSegment(1.0f, 0.0f, 1.0f, 0.5f, 100)),
+ /* repeatIndex= */ 1);
+
+
+ VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+ new PwleSegment(0.0f, 0.16522837f, 63.52442f, 281.7622f, 20),
+ new PwleSegment(0.16522837f, 1.0f, 281.7622f, 500f, 100),
+ new PwleSegment(1.0f, 0.0f, 500, 281.7622f, 100)),
+ /* repeatIndex= */ 1);
+
+ SparseArray<VibratorController> vibrators = new SparseArray<>();
+ vibrators.put(PWLE_V2_VIBRATOR_ID,
+ createPwleV2VibratorController(PWLE_V2_VIBRATOR_ID, TEST_BASIC_FREQUENCIES_HZ,
+ TEST_BASIC_OUTPUT_ACCELERATIONS_GS));
+ DeviceAdapter adapter = new DeviceAdapter(mVibrationSettings, vibrators);
+
+ assertThat(adapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isEqualTo(expected);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithRelativeDelay_withoutFlag_returnsNull() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_TICK, 1, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1, 100, DELAY_TYPE_RELATIVE_START_OFFSET)),
+ /* repeatIndex= */ -1);
+
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isNull();
+ assertThat(mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect)).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testUnsupportedPrimitives_withFlag_returnsNull() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_TICK, 1, 10),
+ new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1, 100)),
+ /* repeatIndex= */ -1);
+
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithRelativeDelay_returnsPrimitiveWithPauseDelays() {
+ int expectedPause = 50;
+ int relativeDelay = 50 + TEST_PRIMITIVE_DURATION - 1;
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ // Originally requested (overlapping):
+ // tick @ 10ms / tick @ 11ms / click @ 69ms + 20ms pause + click
+ // Actually played:
+ // 10ms pause + tick + 50ms pause + click + 20ms pause + click
+ new PrimitiveSegment(PRIMITIVE_TICK, 1, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 1, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1, relativeDelay,
+ DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.5f, 20, DELAY_TYPE_PAUSE)),
+ /* repeatIndex= */ -1);
+
+ // Delay based on primitive duration
+ VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_TICK, 1, 10, DELAY_TYPE_PAUSE),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1, expectedPause, DELAY_TYPE_PAUSE),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.5f, 20, DELAY_TYPE_PAUSE)),
+ /* repeatIndex= */ -1);
+
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isNull();
+ assertThat(mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect)).isEqualTo(expected);
+ }
+
private VibratorController createEmptyVibratorController(int vibratorId) {
return new FakeVibratorControllerProvider(mTestLooper.getLooper())
.newVibratorController(vibratorId, (id, vibrationId) -> {});
}
+ private VibratorController createBasicVibratorController(int vibratorId) {
+ FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+ IVibrator.CAP_COMPOSE_EFFECTS);
+ return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ }
+
private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) {
- FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
- mTestLooper.getLooper());
- provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+ IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
}
private VibratorController createPwleVibratorController(int vibratorId) {
- FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
- mTestLooper.getLooper());
- provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+ IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
provider.setResonantFrequency(TEST_RESONANT_FREQUENCY);
provider.setMinFrequency(TEST_MIN_FREQUENCY);
provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION);
provider.setMaxAmplitudes(TEST_AMPLITUDE_MAP);
return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
}
+
+ private VibratorController createPwleV2VibratorController(int vibratorId) {
+ return createPwleV2VibratorController(vibratorId, TEST_FREQUENCIES_HZ,
+ TEST_OUTPUT_ACCELERATIONS_GS);
+ }
+
+ private VibratorController createPwleV2VibratorController(int vibratorId, float[] frequencies,
+ float[] accelerations) {
+ FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+ IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ provider.setResonantFrequency(TEST_RESONANT_FREQUENCY);
+ provider.setFrequenciesHz(frequencies);
+ provider.setOutputAccelerationsGs(accelerations);
+ provider.setMaxEnvelopeEffectSize(TEST_MAX_ENVELOPE_EFFECT_SIZE);
+ provider.setMinEnvelopeEffectControlPointDurationMillis(
+ TEST_MIN_ENVELOPE_EFFECT_CONTROL_POINT_DURATION_MILLIS);
+
+ return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ }
+
+ private FakeVibratorControllerProvider createVibratorProviderWithEffects(int... capabilities) {
+ FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
+ mTestLooper.getLooper());
+ provider.setCapabilities(capabilities);
+ provider.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+ PRIMITIVE_SPIN, PRIMITIVE_QUICK_RISE, PRIMITIVE_QUICK_FALL, PRIMITIVE_SLOW_RISE);
+ provider.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+ provider.setPrimitiveDuration(TEST_PRIMITIVE_DURATION);
+ return provider;
+ }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index f7127df0ee33..3b2f532dddd7 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -453,20 +453,7 @@ public class HapticFeedbackVibrationProviderTest {
}
@Test
- public void testVibrationAttribute_scrollFeedback_inputCustomizedFlag_useTouchUsage() {
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
- HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
-
- for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributes(effectId, /* flags */
- 0, /* privFlags */ 0);
- assertWithMessage("Expected USAGE_TOUCH for scroll effect " + effectId
- + ", if no input customization").that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
- }
- }
-
- @Test
- public void testVibrationAttribute_scrollFeedback_noInputCustomizedFlag_useHardwareFeedback() {
+ public void testVibrationAttribute_scrollFeedback_useHardwareFeedback() {
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java
new file mode 100644
index 000000000000..f4a6f82fba47
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class PrimitiveDelayAdapterTest {
+ private static final VibratorInfo EMPTY_VIBRATOR_INFO = new VibratorInfo.Builder(0).build();
+ private static final VibratorInfo BASIC_VIBRATOR_INFO = createVibratorInfoWithPrimitives(
+ new int[] { PRIMITIVE_CLICK, PRIMITIVE_TICK },
+ new int[] { 20, 10 });
+
+ private PrimitiveDelayAdapter mAdapter;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mAdapter = new PrimitiveDelayAdapter();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveSegments_flagDisabled_keepsListUnchanged() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 100, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10, DELAY_TYPE_PAUSE)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+ assertEquals(1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, 1));
+
+ assertEquals(originalSegments, segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testNonPrimitiveSegments_keepsListUnchanged() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+ new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+ /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 1, /* duration= */ 20),
+ new PrebakedSegment(VibrationEffect.EFFECT_CLICK, false,
+ VibrationEffect.EFFECT_STRENGTH_LIGHT)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+ assertEquals(1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, 1));
+
+ assertEquals(originalSegments, segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithPause_keepsListUnchanged() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 100, DELAY_TYPE_PAUSE),
+ new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10, DELAY_TYPE_PAUSE)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+ assertEquals(1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, 1));
+
+ assertEquals(originalSegments, segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithRelativeDelay_afterPrimitive_usesPrimitiveStartTimeForDelay() {
+ VibratorInfo info = createVibratorInfoWithPrimitives(
+ new int[] { PRIMITIVE_CLICK }, new int[] { 20 });
+
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.2f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.3f, 0, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.4f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+ List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_PAUSE),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.4f, 0, DELAY_TYPE_PAUSE)));
+
+ // Repeat index is fixed after removals
+ assertEquals(-1, mAdapter.adaptToVibrator(info, segments, -1));
+
+ assertEquals(expectedSegments, segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithRelativeDelay_afterRepeatIndex_usesPauseAsFirstDelay() {
+ VibratorInfo info = createVibratorInfoWithPrimitives(
+ new int[] { PRIMITIVE_CLICK }, new int[] { 20 });
+
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.2f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.3f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.4f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+ List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_PAUSE),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 0.3f, 10, DELAY_TYPE_PAUSE)));
+
+ // Relative offset reset after repeat index.
+ assertEquals(1, mAdapter.adaptToVibrator(info, segments, 2));
+
+ assertEquals(expectedSegments, segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithRelativeDelayAfter_afterStep_usesSegmentStartTimeForDelay() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+ List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 0, DELAY_TYPE_PAUSE)));
+
+ assertEquals(-1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, -1));
+ assertEquals(expectedSegments, segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void testPrimitiveWithRelativeDelayAfter_afterUnknownDuration_usesZeroAsDuration() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new PrebakedSegment(VibrationEffect.EFFECT_POP, false,
+ VibrationEffect.EFFECT_STRENGTH_STRONG),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+ assertEquals(-1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, -1));
+
+ List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+ new PrebakedSegment(VibrationEffect.EFFECT_POP, false,
+ VibrationEffect.EFFECT_STRENGTH_STRONG),
+ new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 10, DELAY_TYPE_PAUSE)));
+
+ assertEquals(expectedSegments, segments);
+ }
+
+ private static VibratorInfo createVibratorInfoWithPrimitives(int[] ids, int[] durations) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(0)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+
+ for (int i = 0; i < ids.length; i++) {
+ builder.setSupportedPrimitive(ids[i], durations[i]);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 867c061151b4..96f0fda263f1 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -26,8 +26,11 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -39,8 +42,8 @@ public class RampToStepAdapterTest {
private static final int TEST_STEP_DURATION = 5;
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
- new VibratorInfo.FrequencyProfile(
+ private static final VibratorInfo.FrequencyProfileLegacy TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfileLegacy(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo();
@@ -49,6 +52,9 @@ public class RampToStepAdapterTest {
private RampToStepAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
@@ -87,6 +93,7 @@ public class RampToStepAdapterTest {
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
@@ -121,7 +128,7 @@ public class RampToStepAdapterTest {
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
- .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+ .setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE)
.build();
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
index 6630ccad6189..53e49e0c711f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
@@ -26,8 +26,11 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -41,8 +44,8 @@ public class SplitSegmentsAdapterTest {
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
- new VibratorInfo.FrequencyProfile(
+ private static final VibratorInfo.FrequencyProfileLegacy TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfileLegacy(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
@@ -52,6 +55,9 @@ public class SplitSegmentsAdapterTest {
private SplitSegmentsAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new SplitSegmentsAdapter();
@@ -97,6 +103,7 @@ public class SplitSegmentsAdapterTest {
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testRampSegments_withPwleDurationLimit_splitsLongRampsAndPreserveOtherSegments() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
@@ -125,7 +132,7 @@ public class SplitSegmentsAdapterTest {
VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
.setPwlePrimitiveDurationMax(10)
- .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+ .setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE)
.build();
// Update repeat index to skip the ramp splits.
@@ -137,7 +144,7 @@ public class SplitSegmentsAdapterTest {
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
- .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+ .setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE)
.setPwlePrimitiveDurationMax(PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX)
.build();
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
index 82deff011e82..fae634d0bd39 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -26,8 +26,11 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -38,8 +41,8 @@ import java.util.stream.IntStream;
public class StepToRampAdapterTest {
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
- private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
- new VibratorInfo.FrequencyProfile(
+ private static final VibratorInfo.FrequencyProfileLegacy TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfileLegacy(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo();
@@ -48,6 +51,9 @@ public class StepToRampAdapterTest {
private StepToRampAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new StepToRampAdapter();
@@ -134,6 +140,7 @@ public class StepToRampAdapterTest {
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 100, /* duration= */ 10),
@@ -153,7 +160,7 @@ public class StepToRampAdapterTest {
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
- .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+ .setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE)
.build();
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index e4a6efdd5304..da1c1ae76d2d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -150,14 +150,15 @@ public class VibrationSettingsTest {
// Make sure broadcast receivers are not registered for this test, to avoid flakes.
doReturn(null).when(mContextSpy)
.registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class), anyInt());
+
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, ""));
setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM);
setIgnoreVibrationsOnWirelessCharger(false);
-
mockGoToSleep(/* goToSleepTime= */ 0, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
+
createSystemReadyVibrationSettings();
}
@@ -243,6 +244,7 @@ public class VibrationSettingsTest {
mVibrationSettings.removeListener(mListenerMock);
// Trigger multiple observers manually.
+ mVibrationSettings.mSettingObserver.onChange(false);
mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(USER_ID);
mVibrationSettings.mRingerModeBroadcastReceiver.onReceive(mContextSpy,
@@ -313,6 +315,7 @@ public class VibrationSettingsTest {
doReturn(nonWirelessChargingIntent).when(mContextSpy).registerReceiver(
any(BroadcastReceiver.class),
argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt());
+
setIgnoreVibrationsOnWirelessCharger(true);
createSystemReadyVibrationSettings();
@@ -327,6 +330,7 @@ public class VibrationSettingsTest {
doReturn(wirelessChargingIntent).when(mContextSpy).registerReceiver(
any(BroadcastReceiver.class),
argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt());
+
setIgnoreVibrationsOnWirelessCharger(true);
createSystemReadyVibrationSettings();
@@ -447,8 +451,11 @@ public class VibrationSettingsTest {
@Test
public void shouldIgnoreVibration_withoutAudioManager_allowsAllVibrations() {
- when(mContextSpy.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(null);
- createSystemReadyVibrationSettings();
+ mVibrationSettings = new VibrationSettings(mContextSpy,
+ new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
+ mVibrationSettings.onSystemReady(mPackageManagerInternalMock,
+ mPowerManagerInternalMock, mActivityManagerMock, mVirtualDeviceManagerInternalMock,
+ /* audioManager= */ null);
for (int usage : ALL_USAGES) {
assertVibrationNotIgnoredForUsage(usage);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 31cc50f18299..b4345b62c251 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -29,13 +29,11 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -49,7 +47,6 @@ import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
import android.os.Handler;
-import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
@@ -62,13 +59,17 @@ import android.os.test.TestLooper;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -96,8 +97,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -118,12 +117,13 @@ public class VibrationThreadTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
- @Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
@Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
@@ -187,11 +187,11 @@ public class VibrationThreadTest {
mVibratorProviders.clear();
CombinedVibration effect = CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
+ verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
}
@Test
@@ -200,11 +200,11 @@ public class VibrationThreadTest {
.addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
+ verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
}
@Test
@@ -212,34 +212,34 @@ public class VibrationThreadTest {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude() {
VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@@ -249,17 +249,17 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(15)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(1, 2, 3),
mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@@ -277,13 +277,13 @@ public class VibrationThreadTest {
mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
CompletableFuture<Void> mRequestVibrationParamsFuture = CompletableFuture.completedFuture(
null);
- long vibrationId = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture,
+ HalVibration vibration = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture,
USAGE_RINGTONE);
waitForCompletion();
verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID);
assertEquals(Arrays.asList(expectedOneShot(15)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
for (int i = 0; i < amplitudes.size(); i++) {
assertTrue(amplitudes.get(i) < 1 / 255f);
@@ -301,12 +301,13 @@ public class VibrationThreadTest {
new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
CompletableFuture<Void> neverCompletingFuture = new CompletableFuture<>();
- long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
+ HalVibration vibration = startThreadAndDispatcher(effect, neverCompletingFuture,
+ USAGE_RINGTONE);
waitForCompletion();
verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID);
assertEquals(Arrays.asList(expectedOneShot(15)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(1, 1, 1),
mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@@ -319,13 +320,13 @@ public class VibrationThreadTest {
int[] amplitudes = new int[]{1, 2, 3};
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(
waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
TEST_TIMEOUT_MILLIS));
// Vibration still running after 2 cycles.
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
@@ -333,15 +334,15 @@ public class VibrationThreadTest {
/* uid= */ 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
mVibrationConductor.notifyCancelled(cancelVibrationInfo, /* immediate= */ false);
waitForCompletion();
- assertFalse(mThread.isRunningVibrationId(vibrationId));
+ assertFalse(mThread.isRunningVibrationId(vibration.id));
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_SUPERSEDED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
- assertFalse(fakeVibrator.getEffectSegments(vibrationId).isEmpty());
+ assertFalse(fakeVibrator.getEffectSegments(vibration.id).isEmpty());
assertFalse(playedAmplitudes.isEmpty());
for (int i = 0; i < playedAmplitudes.size(); i++) {
@@ -358,17 +359,17 @@ public class VibrationThreadTest {
int[] amplitudes = new int[]{1, 2, 3};
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{1, 10, 100}, amplitudes, 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(5000)),
- fakeVibrator.getEffectSegments(vibrationId));
+ fakeVibrator.getEffectSegments(vibration.id));
}
@Test
@@ -377,16 +378,16 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
/* timings= */ new long[]{0, 100, 50, 100, 0, 0, 0, 50}, /* repeat= */ -1);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
- assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
.isEqualTo(expectedOneShots(100L, 150L));
}
@@ -398,16 +399,16 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
/* timings= */ new long[]{0, 100, 0, 50, 50, 0, 100, 50}, amplitudes,
/* repeat= */ -1);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
- assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
.isEqualTo(expectedOneShots(200L, 50L));
}
@@ -420,7 +421,7 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
/* timings= */ new long[]{0, 200, 50, 100, 0, 50, 50, 100}, /* repeat= */ 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
// We are expect this test to repeat the vibration effect twice, which would result in 5
// segments being played:
// 200ms ON
@@ -428,16 +429,17 @@ public class VibrationThreadTest {
// 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
// 150ms ON (100ms + 50ms, skips 0ms)
// 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
- assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5,
+ assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() >= 5,
5000L + TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
- assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5))
+ assertThat(
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).subList(0, 5))
.isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L));
}
@@ -458,18 +460,18 @@ public class VibrationThreadTest {
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
.compose();
- long vibrationId = startThreadAndDispatcher(repeatingEffect);
+ HalVibration vibration = startThreadAndDispatcher(repeatingEffect);
- assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+ assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
// PWLE size max was used to generate a single vibrate call with 10 segments.
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
- assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+ assertEquals(10, fakeVibrator.getEffectSegments(vibration.id).size());
}
@Test
@@ -487,18 +489,18 @@ public class VibrationThreadTest {
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
.compose();
- long vibrationId = startThreadAndDispatcher(repeatingEffect);
+ HalVibration vibration = startThreadAndDispatcher(repeatingEffect);
- assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+ assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
waitForCompletion();
// Composition size max was used to generate a single vibrate call with 10 primitives.
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
- assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+ assertEquals(10, fakeVibrator.getEffectSegments(vibration.id).size());
}
@Test
@@ -510,17 +512,17 @@ public class VibrationThreadTest {
int[] amplitudes = new int[]{1, 2, 3};
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5000, 500, 50}, amplitudes, 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(5550)),
- fakeVibrator.getEffectSegments(vibrationId));
+ fakeVibrator.getEffectSegments(vibration.id));
}
@LargeTest
@@ -534,17 +536,17 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
/* timings= */ new long[]{expectedOnDuration - 100, 50},
/* amplitudes= */ new int[]{1, 2}, /* repeat= */ 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
- assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
+ assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() > 1,
expectedOnDuration + TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
- List<VibrationEffectSegment> effectSegments = fakeVibrator.getEffectSegments(vibrationId);
+ List<VibrationEffectSegment> effectSegments = fakeVibrator.getEffectSegments(vibration.id);
// First time, turn vibrator ON for the expected fixed duration.
assertEquals(expectedOnDuration, effectSegments.get(0).getDuration());
// Vibrator turns off in the middle of the second execution of the first step. Expect it to
@@ -567,11 +569,11 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.compose();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -584,7 +586,7 @@ public class VibrationThreadTest {
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SETTINGS_UPDATE);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -597,11 +599,11 @@ public class VibrationThreadTest {
mVibratorProviders.get(VIBRATOR_ID).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -614,7 +616,7 @@ public class VibrationThreadTest {
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SETTINGS_UPDATE);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -624,11 +626,11 @@ public class VibrationThreadTest {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -641,7 +643,7 @@ public class VibrationThreadTest {
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -650,17 +652,17 @@ public class VibrationThreadTest {
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD);
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
}
@Test
@@ -670,32 +672,32 @@ public class VibrationThreadTest {
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
HalVibration vibration = createVibration(CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
- vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
- long vibrationId = startThreadAndDispatcher(vibration);
+ vibration.fillFallbacks(unused -> fallback);
+ startThreadAndDispatcher(vibration);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
- assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
+ verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@Test
@@ -704,17 +706,17 @@ public class VibrationThreadTest {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID),
eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
- assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id))
.containsExactly(effect)
.inOrder();
}
@@ -730,33 +732,50 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
- fakeVibrator.getEffectSegments(vibrationId));
+ fakeVibrator.getEffectSegments(vibration.id));
}
@Test
- public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
+ @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.compose();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
- assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
+ verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .compose();
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@Test
@@ -774,16 +793,17 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
.compose();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
// Vibrator compose called twice.
- verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size());
+ verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ assertEquals(3, fakeVibrator.getEffectSegments(vibration.id).size());
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
@@ -810,14 +830,14 @@ public class VibrationThreadTest {
.build())
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.compose();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedOneShot(10),
@@ -829,7 +849,7 @@ public class VibrationThreadTest {
expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@@ -850,19 +870,19 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
- vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback);
- long vibrationId = startThreadAndDispatcher(vibration);
+ vibration.fillFallbacks(unused -> fallback);
+ startThreadAndDispatcher(vibration);
waitForCompletion();
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<VibrationEffectSegment> segments =
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId);
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id);
assertTrue("Wrong segments: " + segments, segments.size() >= 4);
assertTrue(segments.get(0) instanceof PrebakedSegment);
assertTrue(segments.get(1) instanceof PrimitiveSegment);
@@ -874,6 +894,114 @@ public class VibrationThreadTest {
}
@Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void vibrate_singleVibratorPwle_runsComposePwleV2() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ fakeVibrator.setResonantFrequency(150);
+ fakeVibrator.setFrequenciesHz(new float[]{30f, 50f, 100f, 120f, 150f});
+ fakeVibrator.setOutputAccelerationsGs(new float[]{0.3f, 0.5f, 1.0f, 0.8f, 0.6f});
+ fakeVibrator.setMaxEnvelopeEffectSize(10);
+ fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
+
+ VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+ .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*durationMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*durationMillis=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*durationMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*durationMillis=*/ 30)
+ .build();
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+ assertEquals(Arrays.asList(
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 0),
+ expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+ expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+ ), fakeVibrator.getEffectPwlePoints(vibration.id));
+
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void vibrate_singleVibratorBasicPwle_runsComposePwleV2() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ fakeVibrator.setResonantFrequency(150);
+ fakeVibrator.setFrequenciesHz(new float[]{50f, 100f, 120f, 150f});
+ fakeVibrator.setOutputAccelerationsGs(new float[]{0.05f, 1.0f, 3.0f, 2.0f});
+ fakeVibrator.setMaxEnvelopeEffectSize(10);
+ fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
+
+ VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+ .setInitialSharpness(/*initialSharpness=*/ 1.0f)
+ .addControlPoint(/*intensity=*/ 1.0f, /*sharpness=*/ 1.0f, /*durationMillis=*/ 20)
+ .addControlPoint(/*intensity=*/ 1.0f, /*sharpness=*/ 1.0f, /*durationMillis=*/ 100)
+ .addControlPoint(/*intensity=*/ 0.0f, /*sharpness=*/ 1.0f, /*durationMillis=*/ 100)
+ .build();
+
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(220L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+ assertEquals(Arrays.asList(
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 0),
+ expectedPwle(/*amplitude=*/ 1.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 1.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 100),
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 100)
+ ), fakeVibrator.getEffectPwlePoints(vibration.id));
+
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ fakeVibrator.setResonantFrequency(150);
+ fakeVibrator.setFrequenciesHz(new float[]{30f, 50f, 100f, 120f, 150f});
+ fakeVibrator.setOutputAccelerationsGs(new float[]{0.3f, 0.5f, 1.0f, 0.8f, 0.6f});
+ fakeVibrator.setMaxEnvelopeEffectSize(10);
+ fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
+
+ VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+ .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*durationMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*durationMillis=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*durationMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*durationMillis=*/ 30)
+ .build();
+
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+ assertEquals(Arrays.asList(
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 30f, /*timeMillis=*/ 0),
+ expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+ expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+ ), fakeVibrator.getEffectPwlePoints(vibration.id));
+
+ }
+
+ @Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwle() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -891,13 +1019,13 @@ public class VibrationThreadTest {
.addSustain(Duration.ofMillis(30))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
.build();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
@@ -907,8 +1035,8 @@ public class VibrationThreadTest {
expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f,
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200,
/* duration= */ 40)),
- fakeVibrator.getEffectSegments(vibrationId));
- assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibrationId));
+ fakeVibrator.getEffectSegments(vibration.id));
+ assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibration.id));
}
@Test
@@ -932,15 +1060,15 @@ public class VibrationThreadTest {
.addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
.build();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
- verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
+ verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ assertEquals(6, fakeVibrator.getEffectSegments(vibration.id).size());
}
@Test
@@ -949,28 +1077,29 @@ public class VibrationThreadTest {
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS));
// Vibration still running after 2 cycles.
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
}
@Test
public void vibrate_singleVibrator_skipsSyncedCallbacks() {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- long vibrationId = startThreadAndDispatcher(VibrationEffect.createOneShot(10, 100));
+ HalVibration vibration = startThreadAndDispatcher(VibrationEffect.createOneShot(10, 100));
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
verify(mManagerHooks, never()).cancelSyncedVibration();
@@ -984,18 +1113,18 @@ public class VibrationThreadTest {
.addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
}
@Test
@@ -1007,26 +1136,26 @@ public class VibrationThreadTest {
CombinedVibration effect = CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
assertEquals(Arrays.asList(expected),
- mVibratorProviders.get(1).getEffectSegments(vibrationId));
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expected),
- mVibratorProviders.get(2).getEffectSegments(vibrationId));
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expected),
- mVibratorProviders.get(3).getEffectSegments(vibrationId));
+ mVibratorProviders.get(3).getEffectSegments(vibration.id));
}
@Test
@@ -1049,32 +1178,32 @@ public class VibrationThreadTest {
new long[]{10, 10}, new int[]{1, 2}, -1))
.addVibrator(4, composed)
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
assertFalse(mControllers.get(4).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(1).getEffectSegments(vibrationId));
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(2).getEffectSegments(vibrationId));
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
assertEquals(Arrays.asList(expectedOneShot(20)),
- mVibratorProviders.get(3).getEffectSegments(vibrationId));
+ mVibratorProviders.get(3).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
- mVibratorProviders.get(4).getEffectSegments(vibrationId));
+ mVibratorProviders.get(4).getEffectSegments(vibration.id));
}
@Test
@@ -1094,13 +1223,13 @@ public class VibrationThreadTest {
.addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50)
.addNext(2, composed, /* delay= */ 50)
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
InOrder controllerVerifier = inOrder(mControllerCallbacks);
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
+ controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
+ controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
InOrder batteryVerifier = inOrder(mManagerHooks);
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -1110,19 +1239,19 @@ public class VibrationThreadTest {
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(1).getEffectSegments(vibrationId));
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
- mVibratorProviders.get(2).getEffectSegments(vibrationId));
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(3).getEffectSegments(vibrationId));
+ mVibratorProviders.get(3).getEffectSegments(vibration.id));
}
@Test
@@ -1143,30 +1272,29 @@ public class VibrationThreadTest {
CombinedVibration effect = CombinedVibration.createParallel(composed);
// We create the HalVibration here to obtain the vibration id and use it to mock the
// required response when calling triggerSyncedVibration.
- HalVibration halVibration = createVibration(effect);
- long vibrationId = halVibration.id;
- when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
- startThreadAndDispatcher(halVibration);
+ HalVibration vibration = createVibration(effect);
+ when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(true);
+ startThreadAndDispatcher(vibration);
assertTrue(waitUntil(
- () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
- && !mVibratorProviders.get(2).getEffectSegments(vibrationId).isEmpty(),
+ () -> !mVibratorProviders.get(1).getEffectSegments(vibration.id).isEmpty()
+ && !mVibratorProviders.get(2).getEffectSegments(vibration.id).isEmpty(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifySyncedVibrationComplete();
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id));
verify(mManagerHooks, never()).cancelSyncedVibration();
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
VibrationEffectSegment expected = expectedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
assertEquals(Arrays.asList(expected),
- mVibratorProviders.get(1).getEffectSegments(vibrationId));
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expected),
- mVibratorProviders.get(2).getEffectSegments(vibrationId));
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
}
@Test
@@ -1190,10 +1318,9 @@ public class VibrationThreadTest {
.combine();
// We create the HalVibration here to obtain the vibration id and use it to mock the
// required response when calling triggerSyncedVibration.
- HalVibration halVibration = createVibration(effect);
- long vibrationId = halVibration.id;
- when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
- startThreadAndDispatcher(halVibration);
+ HalVibration vibration = createVibration(effect);
+ when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(true);
+ startThreadAndDispatcher(vibration);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC
@@ -1204,9 +1331,9 @@ public class VibrationThreadTest {
| IVibratorManager.CAP_MIXED_TRIGGER_PERFORM
| IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id));
verify(mManagerHooks, never()).cancelSyncedVibration();
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
}
@Test
@@ -1221,19 +1348,19 @@ public class VibrationThreadTest {
.addVibrator(1, VibrationEffect.createOneShot(10, 100))
.addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibration.id));
verify(mManagerHooks, never()).cancelSyncedVibration();
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(1).getEffectSegments(vibrationId));
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
assertEquals(Arrays.asList(expectedOneShot(5)),
- mVibratorProviders.get(2).getEffectSegments(vibrationId));
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
}
@@ -1250,10 +1377,9 @@ public class VibrationThreadTest {
.combine();
// We create the HalVibration here to obtain the vibration id and use it to mock the
// required response when calling triggerSyncedVibration.
- HalVibration halVibration = createVibration(effect);
- long vibrationId = halVibration.id;
- when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false);
- startThreadAndDispatcher(halVibration);
+ HalVibration vibration = createVibration(effect);
+ when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(false);
+ startThreadAndDispatcher(vibration);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC
@@ -1262,7 +1388,7 @@ public class VibrationThreadTest {
| IVibratorManager.CAP_MIXED_TRIGGER_ON
| IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id));
verify(mManagerHooks).cancelSyncedVibration();
assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
}
@@ -1282,7 +1408,7 @@ public class VibrationThreadTest {
.addVibrator(3, VibrationEffect.createWaveform(
new long[]{60}, new int[]{6}, -1))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
// All vibrators are turned on in parallel.
assertTrue(waitUntil(
@@ -1295,20 +1421,20 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(25)),
- mVibratorProviders.get(1).getEffectSegments(vibrationId));
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedOneShot(80)),
- mVibratorProviders.get(2).getEffectSegments(vibrationId));
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedOneShot(60)),
- mVibratorProviders.get(3).getEffectSegments(vibrationId));
+ mVibratorProviders.get(3).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
@@ -1327,17 +1453,11 @@ public class VibrationThreadTest {
CombinedVibration.createParallel(
VibrationEffect.createOneShot(
expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
- CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
- doAnswer(unused -> {
- vibrationCompleteLatch.countDown();
- return null;
- }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
startThreadAndDispatcher(vibration);
long startTime = SystemClock.elapsedRealtime();
- assertTrue(vibrationCompleteLatch.await(expectedDuration + TEST_TIMEOUT_MILLIS,
- TimeUnit.MILLISECONDS));
+ vibration.waitForEnd();
long vibrationEndTime = SystemClock.elapsedRealtime();
waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS);
@@ -1363,17 +1483,11 @@ public class VibrationThreadTest {
CombinedVibration.createParallel(
VibrationEffect.createOneShot(
expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
- CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
- doAnswer(unused -> {
- vibrationCompleteLatch.countDown();
- return null;
- }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
startThreadAndDispatcher(vibration);
long startTime = SystemClock.elapsedRealtime();
- assertTrue(vibrationCompleteLatch.await(callbackDelay + TEST_TIMEOUT_MILLIS,
- TimeUnit.MILLISECONDS));
+ vibration.waitForEnd();
long vibrationEndTime = SystemClock.elapsedRealtime();
waitForCompletion(TEST_TIMEOUT_MILLIS);
@@ -1397,17 +1511,11 @@ public class VibrationThreadTest {
CombinedVibration.createParallel(
VibrationEffect.createOneShot(
expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
- CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
- doAnswer(unused -> {
- vibrationCompleteLatch.countDown();
- return null;
- }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
startThreadAndDispatcher(vibration);
long startTime = SystemClock.elapsedRealtime();
- assertTrue(vibrationCompleteLatch.await(callbackTimeout + TEST_TIMEOUT_MILLIS,
- TimeUnit.MILLISECONDS));
+ vibration.waitForEnd();
long vibrationEndTime = SystemClock.elapsedRealtime();
waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
@@ -1461,11 +1569,11 @@ public class VibrationThreadTest {
fakeVibrator.setOnLatency(latency);
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
- assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+ assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(cancellingThread).
@@ -1480,7 +1588,7 @@ public class VibrationThreadTest {
// After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -1500,10 +1608,10 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.compose())
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -1516,7 +1624,7 @@ public class VibrationThreadTest {
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1534,10 +1642,10 @@ public class VibrationThreadTest {
.addVibrator(1, VibrationEffect.createVendorEffect(createTestVendorData()))
.addVibrator(2, VibrationEffect.createVendorEffect(createTestVendorData()))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -1550,7 +1658,7 @@ public class VibrationThreadTest {
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1566,12 +1674,12 @@ public class VibrationThreadTest {
new long[]{100, 100}, new int[]{1, 2}, 0))
.addVibrator(2, VibrationEffect.createOneShot(100, 100))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(1).isVibrating()
&& mControllers.get(2).isVibrating(),
TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -1584,7 +1692,7 @@ public class VibrationThreadTest {
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1592,19 +1700,18 @@ public class VibrationThreadTest {
@Test
public void vibrate_binderDied_cancelsVibration() throws Exception {
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
- verify(mVibrationToken).linkToDeath(same(mVibrationConductor), eq(0));
- verify(mVibrationToken).unlinkToDeath(same(mVibrationConductor), eq(0));
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED);
- assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
+ assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@@ -1615,15 +1722,15 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
// Duration extended for 5 + 5 + 5 + 15.
assertEquals(Arrays.asList(expectedOneShot(30)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
assertTrue(amplitudes.size() > 3);
assertEquals(expectedAmplitudes(60, 120, 240), amplitudes.subList(0, 3));
@@ -1633,24 +1740,24 @@ public class VibrationThreadTest {
}
@Test
- public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() {
+ public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds()
+ throws Exception {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(10_000);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibrationEffect effect = VibrationEffect.createOneShot(10, 200);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
// Vibration completed but vibrator not yet released.
- verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
- eq(new Vibration.EndInfo(Status.FINISHED)));
+ vibration.waitForEnd();
verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
// Thread still running ramp down.
- assertTrue(mThread.isRunningVibrationId(vibrationId));
+ assertTrue(mThread.isRunningVibrationId(vibration.id));
// Duration extended for 10 + 10000.
assertEquals(Arrays.asList(expectedOneShot(10_010)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
// Will stop the ramp down right away.
mVibrationConductor.notifyCancelled(
@@ -1658,9 +1765,8 @@ public class VibrationThreadTest {
waitForCompletion();
// Does not cancel already finished vibration, but releases vibrator.
- verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
- eq(new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE)));
- verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
+ assertThat(vibration.getStatus()).isNotEqualTo(Status.CANCELLED_BY_SETTINGS_UPDATE);
+ verify(mManagerHooks).onVibrationThreadReleased(vibration.id);
}
@Test
@@ -1670,18 +1776,18 @@ public class VibrationThreadTest {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
+ verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
// Duration extended for 10000 + 15.
assertEquals(Arrays.asList(expectedOneShot(10_015)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
assertTrue(amplitudes.size() > 1);
for (int i = 1; i < amplitudes.size(); i++) {
@@ -1696,14 +1802,14 @@ public class VibrationThreadTest {
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@@ -1714,13 +1820,13 @@ public class VibrationThreadTest {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
- assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id))
.containsExactly(effect)
.inOrder();
assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
@@ -1737,19 +1843,20 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(
Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
- mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1764,14 +1871,14 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.startWaveform()
.addTransition(Duration.ofMillis(1), targetAmplitude(1))
.build();
- long vibrationId = startThreadAndDispatcher(effect);
+ HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
- verifyCallbacksTriggered(vibrationId, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
- fakeVibrator.getEffectSegments(vibrationId));
+ fakeVibrator.getEffectSegments(vibration.id));
assertTrue(fakeVibrator.getAmplitudes().isEmpty());
}
@@ -1796,69 +1903,117 @@ public class VibrationThreadTest {
VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- long vibrationId1 = startThreadAndDispatcher(effect1);
+ HalVibration vibration1 = startThreadAndDispatcher(effect1);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
- verifyCallbacksTriggered(vibrationId1, Status.FINISHED);
- long vibrationId2 = startThreadAndDispatcher(effect2);
+ HalVibration vibration2 = startThreadAndDispatcher(effect2);
// Effect2 won't complete on its own. Cancel it after a couple of repeats.
Thread.sleep(150); // More than two TICKs.
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
waitForCompletion();
- long vibrationId3 = startThreadAndDispatcher(effect3);
+ HalVibration vibration3 = startThreadAndDispatcher(effect3);
waitForCompletion();
// Effect4 is a long oneshot, but it gets cancelled as fast as possible.
long start4 = System.currentTimeMillis();
- long vibrationId4 = startThreadAndDispatcher(effect4);
+ HalVibration vibration4 = startThreadAndDispatcher(effect4);
mVibrationConductor.notifyCancelled(
new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ true);
waitForCompletion();
long duration4 = System.currentTimeMillis() - start4;
// Effect5 is to show that things keep going after the immediate cancel.
- long vibrationId5 = startThreadAndDispatcher(effect5);
+ HalVibration vibration5 = startThreadAndDispatcher(effect5);
waitForCompletion();
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
// Effect1
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
- verifyCallbacksTriggered(vibrationId1, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration1.id);
+ verifyCallbacksTriggered(vibration1, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- fakeVibrator.getEffectSegments(vibrationId1));
+ fakeVibrator.getEffectSegments(vibration1.id));
// Effect2: repeating, cancelled.
- verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
- verifyCallbacksTriggered(vibrationId2, Status.CANCELLED_BY_USER);
+ verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibration2.id);
+ verifyCallbacksTriggered(vibration2, Status.CANCELLED_BY_USER);
// The exact count of segments might vary, so just check that there's more than 2 and
// all elements are the same segment.
- List<VibrationEffectSegment> actualSegments2 = fakeVibrator.getEffectSegments(vibrationId2);
+ List<VibrationEffectSegment> actualSegments2 =
+ fakeVibrator.getEffectSegments(vibration2.id);
assertTrue(actualSegments2.size() + " > 2", actualSegments2.size() > 2);
for (VibrationEffectSegment segment : actualSegments2) {
assertEquals(expectedPrebaked(VibrationEffect.EFFECT_TICK), segment);
}
// Effect3
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3));
- verifyCallbacksTriggered(vibrationId3, Status.FINISHED);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id));
+ verifyCallbacksTriggered(vibration3, Status.FINISHED);
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
- fakeVibrator.getEffectSegments(vibrationId3));
+ fakeVibrator.getEffectSegments(vibration3.id));
// Effect4: cancelled quickly.
- verifyCallbacksTriggered(vibrationId4, Status.CANCELLED_BY_SCREEN_OFF);
+ verifyCallbacksTriggered(vibration4, Status.CANCELLED_BY_SCREEN_OFF);
assertTrue("Tested duration=" + duration4, duration4 < 2000);
// Effect5: played normally after effect4, which may or may not have played.
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- fakeVibrator.getEffectSegments(vibrationId5));
+ fakeVibrator.getEffectSegments(vibration5.id));
+ }
+
+ @Test
+ public void vibrate_multipleVibratorsSequentialInSession_runsInOrderWithoutDelaysAndNoOffs() {
+ mockVibrators(1, 2, 3);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+ CombinedVibration effect = CombinedVibration.startSequential()
+ .addNext(3,
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ /* delay= */ TEST_TIMEOUT_MILLIS)
+ .addNext(1,
+ VibrationEffect.createWaveform(
+ new long[] {TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS},
+ /* repeat= */ -1),
+ /* delay= */ TEST_TIMEOUT_MILLIS)
+ .addNext(2,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1,
+ /* delay= */ TEST_TIMEOUT_MILLIS)
+ .compose(),
+ /* delay= */ TEST_TIMEOUT_MILLIS)
+ .combine();
+ HalVibration vibration = startThreadAndDispatcher(effect, /* isInSession= */ true);
+
+ // Should not timeout as delays will not affect in session playback time.
+ waitForCompletion();
+
+ // Vibrating state remains ON until session resets it.
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertTrue(mControllers.get(1).isVibrating());
+ assertTrue(mControllers.get(2).isVibrating());
+ assertTrue(mControllers.get(3).isVibrating());
+
+ assertEquals(0, mVibratorProviders.get(1).getOffCount());
+ assertEquals(0, mVibratorProviders.get(2).getOffCount());
+ assertEquals(0, mVibratorProviders.get(3).getOffCount());
+ assertEquals(Arrays.asList(expectedOneShot(TEST_TIMEOUT_MILLIS)),
+ mVibratorProviders.get(1).getEffectSegments(vibration.id));
+ assertEquals(expectedAmplitudes(255), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(Arrays.asList(expectedPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
+ mVibratorProviders.get(2).getEffectSegments(vibration.id));
+ assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+ mVibratorProviders.get(3).getEffectSegments(vibration.id));
}
private void mockVibrators(int... vibratorIds) {
@@ -1875,37 +2030,45 @@ public class VibrationThreadTest {
mVibrationSettings.mSettingObserver.onChange(false);
}
- private long startThreadAndDispatcher(VibrationEffect effect) {
+ private HalVibration startThreadAndDispatcher(VibrationEffect effect) {
return startThreadAndDispatcher(CombinedVibration.createParallel(effect));
}
- private long startThreadAndDispatcher(CombinedVibration effect) {
+ private HalVibration startThreadAndDispatcher(CombinedVibration effect) {
return startThreadAndDispatcher(createVibration(effect));
}
- private long startThreadAndDispatcher(HalVibration vib) {
- return startThreadAndDispatcher(vib, /* requestVibrationParamsFuture= */ null);
+ private HalVibration startThreadAndDispatcher(CombinedVibration effect, boolean isInSession) {
+ return startThreadAndDispatcher(createVibration(effect), isInSession,
+ /* requestVibrationParamsFuture= */ null);
+ }
+
+ private HalVibration startThreadAndDispatcher(HalVibration vib) {
+ return startThreadAndDispatcher(vib, /* isInSession= */ false,
+ /* requestVibrationParamsFuture= */ null);
}
- private long startThreadAndDispatcher(VibrationEffect effect,
+ private HalVibration startThreadAndDispatcher(VibrationEffect effect,
CompletableFuture<Void> requestVibrationParamsFuture, int usage) {
VibrationAttributes attrs = new VibrationAttributes.Builder()
.setUsage(usage)
.build();
- HalVibration vib = new HalVibration(mVibrationToken,
- CombinedVibration.createParallel(effect),
- new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
- return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
+ HalVibration vib = new HalVibration(
+ new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ CombinedVibration.createParallel(effect));
+ return startThreadAndDispatcher(vib, /* isInSession= */ false,
+ requestVibrationParamsFuture);
}
- private long startThreadAndDispatcher(HalVibration vib,
+ private HalVibration startThreadAndDispatcher(HalVibration vib, boolean isInSession,
CompletableFuture<Void> requestVibrationParamsFuture) {
mControllers = createVibratorControllers();
DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
- mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
- mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks);
+ mVibrationConductor = new VibrationStepConductor(vib, isInSession, mVibrationSettings,
+ deviceAdapter, mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture,
+ mManagerHooks);
assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
- return mVibrationConductor.getVibration().id;
+ return mVibrationConductor.getVibration();
}
private boolean waitUntil(BooleanSupplier predicate, long timeout)
@@ -1930,8 +2093,8 @@ public class VibrationThreadTest {
}
private HalVibration createVibration(CombinedVibration effect) {
- return new HalVibration(mVibrationToken, effect,
- new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return new HalVibration(new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ effect);
}
private SparseArray<VibratorController> createVibratorControllers() {
@@ -1988,19 +2151,19 @@ public class VibrationThreadTest {
duration);
}
+ private PwlePoint expectedPwle(float amplitude, float frequencyHz, int timeMillis) {
+ return new PwlePoint(amplitude, frequencyHz, timeMillis);
+ }
+
private List<Float> expectedAmplitudes(int... amplitudes) {
return Arrays.stream(amplitudes)
.mapToObj(amplitude -> amplitude / 255f)
.collect(Collectors.toList());
}
- private void verifyCallbacksTriggered(long vibrationId, Status expectedStatus) {
- verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
- }
-
- private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
- verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
- verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
+ private void verifyCallbacksTriggered(HalVibration vibration, Status expectedStatus) {
+ assertThat(vibration.getStatus()).isEqualTo(expectedStatus);
+ verify(mManagerHooks).onVibrationThreadReleased(vibration.id);
}
private static final class TestLooperAutoDispatcher extends Thread {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index 0d13be6d5ab2..0978f48491cc 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -44,6 +44,7 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import androidx.test.InstrumentationRegistry;
@@ -127,13 +128,13 @@ public class VibratorControllerTest {
public void setExternalControl_withCapability_enablesExternalControl() {
mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorController controller = createController();
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
controller.setExternalControl(true);
- assertTrue(controller.isUnderExternalControl());
+ assertTrue(controller.isVibrating());
controller.setExternalControl(false);
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(true));
@@ -143,10 +144,10 @@ public class VibratorControllerTest {
@Test
public void setExternalControl_withNoCapability_ignoresExternalControl() {
VibratorController controller = createController();
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
controller.setExternalControl(true);
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
verify(mNativeWrapperMock, never()).setExternalControl(anyBoolean());
}
@@ -181,6 +182,38 @@ public class VibratorControllerTest {
}
@Test
+ public void setAmplitude_vibratorIdle_ignoresAmplitude() {
+ VibratorController controller = createController();
+ assertFalse(controller.isVibrating());
+
+ controller.setAmplitude(1);
+ assertEquals(0, controller.getCurrentAmplitude(), /* delta= */ 0);
+ }
+
+ @Test
+ public void setAmplitude_vibratorUnderExternalControl_ignoresAmplitude() {
+ mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorController controller = createController();
+ controller.setExternalControl(true);
+ assertTrue(controller.isVibrating());
+
+ controller.setAmplitude(1);
+ assertEquals(0, controller.getCurrentAmplitude(), /* delta= */ 0);
+ }
+
+ @Test
+ public void setAmplitude_vibratorVibrating_setsAmplitude() {
+ when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ VibratorController controller = createController();
+ controller.on(100, /* vibrationId= */ 1);
+ assertTrue(controller.isVibrating());
+ assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0);
+
+ controller.setAmplitude(1);
+ assertEquals(1, controller.getCurrentAmplitude(), /* delta= */ 0);
+ }
+
+ @Test
public void on_withDuration_turnsVibratorOn() {
when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
@@ -236,6 +269,22 @@ public class VibratorControllerTest {
}
@Test
+ public void on_withComposedPwleV2_performsEffect() {
+ mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
+ VibratorController controller = createController();
+
+ PwlePoint[] primitives = new PwlePoint[]{
+ new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
+ new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
+ };
+ assertEquals(15L, controller.on(primitives, 12));
+ assertTrue(controller.isVibrating());
+
+ verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L));
+ }
+
+ @Test
public void off_turnsOffVibrator() {
when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
@@ -302,13 +351,14 @@ public class VibratorControllerTest {
}
private void mockVibratorCapabilities(int capabilities) {
- VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
+ VibratorInfo.FrequencyProfileLegacy
+ frequencyProfile = new VibratorInfo.FrequencyProfileLegacy(
Float.NaN, Float.NaN, Float.NaN, null);
when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class)))
.then(invocation -> {
((VibratorInfo.Builder) invocation.getArgument(0))
.setCapabilities(capabilities)
- .setFrequencyProfile(frequencyProfile);
+ .setFrequencyProfileLegacy(frequencyProfile);
return true;
});
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index cd057b619000..1d1b4e271e19 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -115,6 +115,6 @@ public class VibratorFrameworkStatsLoggerTest {
}
private static VibrationStats.StatsInfo newEmptyStatsInfo() {
- return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
+ return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats());
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4012575cda88..194d48a80a65 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -16,8 +16,6 @@
package com.android.server.vibrator;
-import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
@@ -28,6 +26,7 @@ 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.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -36,6 +35,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -84,22 +84,21 @@ import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.FakeVibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
-import android.view.flags.Flags;
import androidx.test.InstrumentationRegistry;
@@ -168,9 +167,7 @@ public class VibratorManagerServiceTest {
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private VibratorManagerService.NativeWrapper mNativeWrapperMock;
@@ -201,6 +198,7 @@ public class VibratorManagerServiceTest {
new SparseArray<>();
private final List<HalVibration> mPendingVibrations = new ArrayList<>();
+ private final List<VendorVibrationSession> mPendingSessions = new ArrayList<>();
private VibratorManagerService mService;
private Context mContextSpy;
@@ -266,12 +264,19 @@ public class VibratorManagerServiceTest {
@After
public void tearDown() throws Exception {
if (mService != null) {
- if (!mPendingVibrations.stream().allMatch(HalVibration::hasEnded)) {
- // Cancel any pending vibration from tests.
- cancelVibrate(mService);
- for (HalVibration vibration : mPendingVibrations) {
- vibration.waitForEnd();
- }
+ // Make sure we have permission to cancel test vibrations, even if the test denied them.
+ grantPermission(android.Manifest.permission.VIBRATE);
+ // Cancel any pending vibration from tests, including external vibrations.
+ cancelVibrate(mService);
+ // End pending sessions.
+ for (VendorVibrationSession session : mPendingSessions) {
+ session.cancelSession();
+ }
+ // Dispatch and wait for all callbacks in test looper to be processed.
+ stopAutoDispatcherAndDispatchAll();
+ // Wait until pending vibrations end asynchronously.
+ for (HalVibration vibration : mPendingVibrations) {
+ vibration.waitForEnd();
}
// Wait until all vibrators have stopped vibrating, waiting for ramp-down.
// Note: if a test is flaky here something is wrong with the vibration finalization.
@@ -288,8 +293,6 @@ public class VibratorManagerServiceTest {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PowerManagerInternal.class);
LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
- // Ignore potential exceptions about the looper having never dispatched any messages.
- mTestLooper.stopAutoDispatchAndIgnoreExceptions();
if (mInputManagerGlobalSession != null) {
mInputManagerGlobalSession.close();
}
@@ -799,7 +802,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS)
public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -893,7 +896,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
+ @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -1234,6 +1237,39 @@ public class VibratorManagerServiceTest {
.anyMatch(PrebakedSegment.class::isInstance));
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void vibrate_withOngoingHigherImportanceVendorSession_ignoresEffect() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ HalVibration vibration = vibrateAndWaitUntilFinished(service,
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ assertThat(vibration.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
+ verify(callback, never()).onFinishing();
+ verify(callback, never()).onFinished(anyInt());
+ // The second vibration shouldn't have played any prebaked segment.
+ assertFalse(fakeVibrator.getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
+ }
+
@Test
public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
throws Exception {
@@ -1294,6 +1330,40 @@ public class VibratorManagerServiceTest {
.filter(PrebakedSegment.class::isInstance).count());
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void vibrate_withOngoingLowerImportanceVendorSession_cancelsOngoingSession()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ HalVibration vibration = vibrateAndWaitUntilFinished(service,
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+ assertThat(vibration.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ // One segment played is the prebaked CLICK from the new vibration.
+ assertEquals(1, mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .filter(PrebakedSegment.class::isInstance).count());
+ }
+
@Test
public void vibrate_withOngoingSameImportancePipelinedVibration_continuesOngoingEffect()
throws Exception {
@@ -1330,6 +1400,37 @@ public class VibratorManagerServiceTest {
}
@Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VIBRATION_PIPELINE_ENABLED)
+ public void vibrate_withPipelineFlagEnabledAndShortEffect_continuesOngoingEffect()
+ throws Exception {
+ assumeTrue(mVibrationConfig.getVibrationPipelineMaxDurationMs() > 0);
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_THUD);
+ fakeVibrator.setPrimitiveDuration(
+ mVibrationConfig.getVibrationPipelineMaxDurationMs() - 1);
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration firstVibration = vibrateWithUid(service, /* uid= */ 123,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose(), HAPTIC_FEEDBACK_ATTRS);
+ HalVibration secondVibration = vibrateWithUid(service, /* uid= */ 456,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .compose(), HAPTIC_FEEDBACK_ATTRS);
+ secondVibration.waitForEnd();
+
+ assertThat(fakeVibrator.getAllEffectSegments()).hasSize(2);
+ assertThat(firstVibration.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(secondVibration.getStatus()).isEqualTo(Status.FINISHED);
+ }
+
+ @Test
public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1390,16 +1491,16 @@ public class VibratorManagerServiceTest {
// The native callback will be dispatched manually in this test.
mTestLooper.stopAutoDispatchAndIgnoreExceptions();
- ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor =
+ ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
ArgumentCaptor.forClass(
- VibratorManagerService.OnSyncedVibrationCompleteListener.class);
+ VibratorManagerService.VibratorManagerNativeCallbacks.class);
verify(mNativeWrapperMock).init(listenerCaptor.capture());
CountDownLatch triggerCountDown = new CountDownLatch(1);
// Mock trigger callback on registered listener right after the synced vibration starts.
when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> {
- listenerCaptor.getValue().onComplete(answer.getArgument(0));
+ listenerCaptor.getValue().onSyncedVibrationComplete(answer.getArgument(0));
triggerCountDown.countDown();
return true;
});
@@ -1436,6 +1537,8 @@ public class VibratorManagerServiceTest {
FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
VibratorManagerService service = createSystemReadyService();
CombinedVibration effect = CombinedVibration.startParallel()
@@ -1511,6 +1614,7 @@ public class VibratorManagerServiceTest {
}
@Test
+ @EnableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API)
public void performHapticFeedback_doesNotRequireVibrateOrBypassPermissions() throws Exception {
// Deny permissions that would have been required for regular vibrations, and check that
// the vibration proceed as expected to verify that haptic feedback does not need these
@@ -1519,8 +1623,6 @@ public class VibratorManagerServiceTest {
denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
- // Flag override to enable the scroll feedack constants to bypass interruption policies.
- mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
mHapticFeedbackVibrationMap.put(
HapticFeedbackConstants.SCROLL_TICK,
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
@@ -1538,12 +1640,15 @@ public class VibratorManagerServiceTest {
PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
VibrationAttributes attrs = vibration.callerInfo.attrs;
- assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrs.getUsage());
assertTrue(attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
assertTrue(attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
}
@Test
+ @EnableFlags({
+ android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API,
+ android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED,
+ })
public void performHapticFeedbackForInputDevice_doesNotRequireVibrateOrBypassPermissions()
throws Exception {
// Deny permissions that would have been required for regular vibrations, and check that
@@ -1553,18 +1658,15 @@ public class VibratorManagerServiceTest {
denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
- // Flag override to enable the scroll feedback constants to bypass interruption policies.
- mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
mHapticFeedbackVibrationMapSourceRotary.put(
HapticFeedbackConstants.SCROLL_TICK,
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
mHapticFeedbackVibrationMapSourceTouchScreen.put(
- HapticFeedbackConstants.DRAG_START,
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+ HapticFeedbackConstants.SCROLL_ITEM_FOCUS,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD));
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
- fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_THUD);
VibratorManagerService service = createSystemReadyService();
HalVibration vibrationByRotary =
@@ -1573,7 +1675,7 @@ public class VibratorManagerServiceTest {
InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
HalVibration vibrationByTouchScreen =
performHapticFeedbackForInputDeviceAndWaitUntilFinished(
- service, HapticFeedbackConstants.DRAG_START, /* inputDeviceId= */ 0,
+ service, HapticFeedbackConstants.SCROLL_ITEM_FOCUS, /* inputDeviceId= */ 0,
InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
@@ -1583,18 +1685,17 @@ public class VibratorManagerServiceTest {
PrebakedSegment segmentByRotary = (PrebakedSegment) playedSegments.get(0);
assertEquals(VibrationEffect.EFFECT_CLICK, segmentByRotary.getEffectId());
VibrationAttributes attrsByRotary = vibrationByRotary.callerInfo.attrs;
- assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrsByRotary.getUsage());
assertTrue(attrsByRotary.isFlagSet(
VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
// Verify feedback by touch screen input
PrebakedSegment segmentByTouchScreen = (PrebakedSegment) playedSegments.get(1);
- assertEquals(VibrationEffect.EFFECT_TICK, segmentByTouchScreen.getEffectId());
+ assertEquals(VibrationEffect.EFFECT_THUD, segmentByTouchScreen.getEffectId());
VibrationAttributes attrsByTouchScreen = vibrationByTouchScreen.callerInfo.attrs;
- assertEquals(VibrationAttributes.USAGE_TOUCH, attrsByTouchScreen.getUsage());
- assertTrue(attrsByRotary.isFlagSet(
+ assertTrue(attrsByTouchScreen.isFlagSet(
VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
- assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+ assertTrue(
+ attrsByTouchScreen.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
}
@Test
@@ -1629,12 +1730,14 @@ public class VibratorManagerServiceTest {
}
@Test
+ @EnableFlags({
+ android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API,
+ android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED,
+ })
public void performHapticFeedbackForInputDevice_restrictedConstantsWithoutPermission_doesNotVibrate()
throws Exception {
// Deny permission to vibrate with restricted constants
denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
- mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
// Public constant, no permission required
mHapticFeedbackVibrationMapSourceRotary.put(
HapticFeedbackConstants.CONFIRM,
@@ -1698,9 +1801,9 @@ public class VibratorManagerServiceTest {
}
@Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED)
public void performHapticFeedbackForInputDevice_restrictedConstantsWithPermission_playsVibration()
throws Exception {
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
// Grant permission to vibrate with restricted constants
grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
// Public constant, no permission required
@@ -1733,9 +1836,11 @@ public class VibratorManagerServiceTest {
}
@Test
+ @EnableFlags({
+ android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API,
+ android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED,
+ })
public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
denyPermission(android.Manifest.permission.VIBRATE);
mHapticFeedbackVibrationMap.put(
HapticFeedbackConstants.KEYBOARD_TAP,
@@ -1768,9 +1873,11 @@ public class VibratorManagerServiceTest {
}
@Test
+ @EnableFlags({
+ android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API,
+ android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED,
+ })
public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
denyPermission(android.Manifest.permission.VIBRATE);
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -1792,29 +1899,7 @@ public class VibratorManagerServiceTest {
}
@Test
- public void performHapticFeedback_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration =
- performHapticFeedbackAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
- public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
- InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
// Deny permission to vibrate with vendor effects
denyPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
@@ -1839,7 +1924,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithPermission_successful() throws Exception {
// Grant permission to vibrate with vendor effects
grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
@@ -1927,7 +2012,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception {
// Keep user settings the same as device default so only adaptive scale is applied.
setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
@@ -1970,7 +2055,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({
+ @EnableFlags({
android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
})
@@ -2038,7 +2123,8 @@ public class VibratorManagerServiceTest {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(1).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK);
VibratorManagerService service = createSystemReadyService();
vibrateAndWaitUntilFinished(service,
@@ -2055,9 +2141,10 @@ public class VibratorManagerServiceTest {
assertTrue(segments.size() > 2);
// 0: Supported effect played
assertTrue(segments.get(0) instanceof PrebakedSegment);
- // 1: No segment for unsupported primitive
+ // 1: Supported primitive played
+ assertTrue(segments.get(1) instanceof PrimitiveSegment);
// 2: One or more intermediate step segments as fallback for unsupported effect
- for (int i = 1; i < segments.size() - 1; i++) {
+ for (int i = 2; i < segments.size() - 1; i++) {
assertTrue(segments.get(i) instanceof StepSegment);
}
// 3: Supported primitive played
@@ -2148,6 +2235,27 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void cancelVibrate_externalVibration_cancelWithDifferentToken() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ IBinder vibrationBinderToken = mock(IBinder.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class), vibrationBinderToken);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ IBinder cancelBinderToken = mock(IBinder.class);
+ mService.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, cancelBinderToken);
+
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ assertEquals(Arrays.asList(false, true, false),
+ mVibratorProviders.get(1).getExternalControlStates());
+ }
+
+ @Test
public void onExternalVibration_ignoreVibrationFromVirtualDevices() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -2219,13 +2327,13 @@ public class VibratorManagerServiceTest {
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, secondScale.scaleLevel);
verify(firstController).mute();
verify(secondController, never()).mute();
- // Set external control called only once.
- assertEquals(Arrays.asList(false, true),
+ // Set external control called for each vibration independently.
+ assertEquals(Arrays.asList(false, true, false, true),
mVibratorProviders.get(1).getExternalControlStates());
mExternalVibratorService.onExternalVibrationStop(secondVibration);
mExternalVibratorService.onExternalVibrationStop(firstVibration);
- assertEquals(Arrays.asList(false, true, false),
+ assertEquals(Arrays.asList(false, true, false, true, false),
mVibratorProviders.get(1).getExternalControlStates());
verify(firstToken).linkToDeath(any(), eq(0));
@@ -2244,7 +2352,7 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
- vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+ HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
// VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -2257,7 +2365,8 @@ public class VibratorManagerServiceTest {
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ vibration.waitForEnd();
+ assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
assertEquals(Arrays.asList(false, true),
mVibratorProviders.get(1).getExternalControlStates());
}
@@ -2288,6 +2397,37 @@ public class VibratorManagerServiceTest {
assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void onExternalVibration_withOngoingHigherImportanceVendorSession_ignoreNewVibration()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
+ mTestLooper.dispatchAll();
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ // External vibration is ignored.
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ // Session still running.
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(callback, never()).onFinishing();
+ verify(callback, never()).onFinished(anyInt());
+ }
+
@Test
public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration()
throws Exception {
@@ -2298,7 +2438,7 @@ public class VibratorManagerServiceTest {
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
new long[]{100, 200, 300}, new int[]{128, 255, 255}, 1);
- vibrate(service, repeatingEffect, ALARM_ATTRS);
+ HalVibration repeatingVibration = vibrate(service, repeatingEffect, ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -2310,7 +2450,8 @@ public class VibratorManagerServiceTest {
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ repeatingVibration.waitForEnd();
+ assertThat(repeatingVibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
assertEquals(Arrays.asList(false, true),
mVibratorProviders.get(1).getExternalControlStates());
}
@@ -2342,6 +2483,38 @@ public class VibratorManagerServiceTest {
assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void onExternalVibration_withOngoingLowerImportanceVendorSession_cancelsOngoingSession()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
+ mTestLooper.dispatchAll();
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ // Session is cancelled.
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ assertEquals(Arrays.asList(false, true),
+ mVibratorProviders.get(1).getExternalControlStates());
+ }
+
@Test
public void onExternalVibration_withRingtone_usesRingerModeSettings() {
mockVibrators(1);
@@ -2418,7 +2591,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
@@ -2465,7 +2638,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @DisableFlags(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
@@ -2585,7 +2758,7 @@ public class VibratorManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
+ @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -2607,6 +2780,675 @@ public class VibratorManagerServiceTest {
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutFeatureFlag_throwsException() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ int vibratorId = 1;
+ mockVibrators(vibratorId);
+ VibratorManagerService service = createSystemReadyService();
+
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ assertThrows("Expected starting session without feature flag to fail!",
+ UnsupportedOperationException.class,
+ () -> startSession(service, RINGTONE_ATTRS, callback, vibratorId));
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ verify(callback, never()).onStarted(any(IVibrationSession.class));
+ verify(callback, never()).onFinishing();
+ verify(callback, never()).onFinished(anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutCapability_doesNotStart() throws Exception {
+ int vibratorId = 1;
+ mockVibrators(vibratorId);
+ VibratorManagerService service = createSystemReadyService();
+
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
+ callback, vibratorId);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ verify(callback, never()).onFinishing();
+ verify(callback)
+ .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutCallback_doesNotStart() {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ int vibratorId = 1;
+ mockVibrators(vibratorId);
+ VibratorManagerService service = createSystemReadyService();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
+ /* callback= */ null, vibratorId);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(session).isNull();
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutVibratorIds_doesNotStart() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ int[] nullIds = null;
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, nullIds);
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+
+ int[] emptyIds = {};
+ session = startSession(service, RINGTONE_ATTRS, callback, emptyIds);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ verify(callback, never()).onFinishing();
+ verify(callback, times(2))
+ .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_badVibratorId_failsToStart() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ when(mNativeWrapperMock.startSession(anyLong(), any(int[].class))).thenReturn(false);
+ doReturn(false).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 3}));
+ doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 2}));
+ VibratorManagerService service = createSystemReadyService();
+
+ IBinder token = mock(IBinder.class);
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ doReturn(token).when(callback).asBinder();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 3);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ verify(callback, never()).onStarted(any(IVibrationSession.class));
+ verify(callback, never()).onFinishing();
+ verify(callback)
+ .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenFinish_returnsSuccessAfterCallback() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ captor.getValue().finishSession();
+ mTestLooper.dispatchAll();
+
+ // Session not ended until HAL callback.
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenSendCancelSignal_cancelsSession() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ session.getCancellationSignal().cancel();
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenCancel_returnsCancelStatus() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ captor.getValue().cancelSession();
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_finishThenCancel_returnsRightAwayWithFinishedStatus()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ captor.getValue().finishSession();
+ mTestLooper.dispatchAll();
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+ captor.getValue().cancelSession();
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenHalCancels_returnsCancelStatus()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+
+ VibratorManagerService service = createSystemReadyService();
+ ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
+ ArgumentCaptor.forClass(
+ VibratorManagerService.VibratorManagerNativeCallbacks.class);
+ verify(mNativeWrapperMock).init(listenerCaptor.capture());
+ doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class));
+
+ IBinder token = mock(IBinder.class);
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ doReturn(token).when(callback).asBinder();
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ // Mock HAL ending session unexpectedly.
+ listenerCaptor.getValue().onVibrationSessionComplete(session.getSessionId());
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withPowerMode_usesPowerModeState() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+ VendorVibrationSession session1 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ VendorVibrationSession session2 = startSession(service, RINGTONE_ATTRS, callback, 1);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+ captor.getValue().cancelSession();
+ mTestLooper.dispatchAll();
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+ VendorVibrationSession session3 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock, never())
+ .startSession(eq(session1.getSessionId()), any(int[].class));
+ verify(mNativeWrapperMock).startSession(eq(session2.getSessionId()), eq(new int[] {1}));
+ verify(mNativeWrapperMock).startSession(eq(session3.getSessionId()), eq(new int[] {1}));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withOngoingHigherImportanceVibration_ignoresSession()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
+ vibrate(service, effect, ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2,
+ service, TEST_TIMEOUT_MILLIS));
+
+ VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock, never())
+ .startSession(eq(session.getSessionId()), any(int[].class));
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
+ verify(callback, never()).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_IGNORED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withOngoingLowerImportanceVibration_cancelsOngoing()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
+ HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ vibration.waitForEnd();
+ assertTrue(waitUntil(s -> session.isStarted(), service, TEST_TIMEOUT_MILLIS));
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 }));
+ verify(callback).onStarted(any(IVibrationSession.class));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withOngoingLowerImportanceExternalVibration_cancelsOngoing()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback = mockSessionCallbacks();
+
+ IBinder firstToken = mock(IBinder.class);
+ IExternalVibrationController controller = mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ controller, firstToken);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ // The external vibration should have been cancelled
+ verify(controller).mute();
+ assertEquals(Arrays.asList(false, true, false),
+ mVibratorProviders.get(1).getExternalControlStates());
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 }));
+ verify(callback).onStarted(any(IVibrationSession.class));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_afterCancel_vibrationIgnored() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.cancelSession();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+ "reason");
+
+ // VibrationThread will never start this vibration.
+ assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_afterFinish_vibrationIgnored() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+ "reason");
+
+ // Session not ended until HAL callback.
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+ // VibrationThread will never start this vibration.
+ assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_repeatingVibration_vibrationIgnored() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(
+ VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, 0)),
+ "reason");
+
+ // VibrationThread will never start this vibration.
+ assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(service.isVibrating(1)).isFalse();
+ assertThat(service.isVibrating(2)).isFalse();
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_singleVibration_playsAllVibrateCommands() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ FakeVibratorControllerProvider fakeVibrator2 = mVibratorProviders.get(1);
+ fakeVibrator2.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(
+ VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, -1)),
+ "reason");
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ // Vibrators will receive 2 requests for the waveform playback
+ assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(s -> fakeVibrator2.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(service.isVibrating(1)).isFalse();
+ assertThat(service.isVibrating(2)).isFalse();
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(1));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_multipleVibrations_playsAllVibrations() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+ "reason");
+
+ // VibrationThread will start this vibration async, so wait until vibration is completed.
+ assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 1, service,
+ TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(s -> !session.getVibrations().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(20, 255)),
+ "reason");
+
+ assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(service.isVibrating(1)).isFalse();
+ assertThat(service.isVibrating(2)).isFalse();
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(2));
+ }
+
+ @Test
public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -2749,7 +3591,8 @@ public class VibratorManagerServiceTest {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(1).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
VibratorManagerService service = createSystemReadyService();
vibrateAndWaitUntilFinished(service,
@@ -2792,10 +3635,12 @@ public class VibratorManagerServiceTest {
assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
// No repetitions in reported effect/primitive IDs.
- assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+ assertArrayEquals(
+ new int[] {
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ },
metrics.halSupportedCompositionPrimitivesUsed);
- assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
- metrics.halUnsupportedCompositionPrimitivesUsed);
assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
metrics.halSupportedEffectsUsed);
assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
@@ -3019,6 +3864,34 @@ public class VibratorManagerServiceTest {
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
+ private IVibrationSessionCallback mockSessionCallbacks() {
+ return mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ }
+
+ private IVibrationSessionCallback mockSessionCallbacks(long delayToEndSessionMillis) {
+ Handler handler = new Handler(mTestLooper.getLooper());
+ ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
+ ArgumentCaptor.forClass(
+ VibratorManagerService.VibratorManagerNativeCallbacks.class);
+ verify(mNativeWrapperMock).init(listenerCaptor.capture());
+ doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class));
+ doAnswer(args -> {
+ handler.postDelayed(
+ () -> listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0)),
+ delayToEndSessionMillis);
+ return null;
+ }).when(mNativeWrapperMock).endSession(anyLong(), eq(false));
+ doAnswer(args -> {
+ listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0));
+ return null;
+ }).when(mNativeWrapperMock).endSession(anyLong(), eq(true));
+
+ IBinder token = mock(IBinder.class);
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ doReturn(token).when(callback).asBinder();
+ return callback;
+ }
+
private void cancelVibrate(VibratorManagerService service) {
service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
}
@@ -3105,9 +3978,20 @@ public class VibratorManagerServiceTest {
return vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs);
}
+ private HalVibration vibrateWithUid(VibratorManagerService service, int uid,
+ VibrationEffect effect, VibrationAttributes attrs) {
+ return vibrateWithUidAndDevice(service, uid, Context.DEVICE_ID_DEFAULT,
+ CombinedVibration.createParallel(effect), attrs);
+ }
+
private HalVibration vibrateWithDevice(VibratorManagerService service, int deviceId,
CombinedVibration effect, VibrationAttributes attrs) {
- HalVibration vib = service.vibrateWithPermissionCheck(UID, deviceId, PACKAGE_NAME, effect,
+ return vibrateWithUidAndDevice(service, UID, deviceId, effect, attrs);
+ }
+
+ private HalVibration vibrateWithUidAndDevice(VibratorManagerService service, int uid,
+ int deviceId, CombinedVibration effect, VibrationAttributes attrs) {
+ HalVibration vib = service.vibrateWithPermissionCheck(uid, deviceId, PACKAGE_NAME, effect,
attrs, "some reason", service);
if (vib != null) {
mPendingVibrations.add(vib);
@@ -3115,6 +3999,16 @@ public class VibratorManagerServiceTest {
return vib;
}
+ private VendorVibrationSession startSession(VibratorManagerService service,
+ VibrationAttributes attrs, IVibrationSessionCallback callback, int... vibratorIds) {
+ VendorVibrationSession session = service.startVendorVibrationSessionInternal(UID,
+ Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, vibratorIds, attrs, "reason", callback);
+ if (session != null) {
+ mPendingSessions.add(session);
+ }
+ return session;
+ }
+
private boolean waitUntil(Predicate<VibratorManagerService> predicate,
VibratorManagerService service, long timeout) throws InterruptedException {
long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
@@ -3126,6 +4020,13 @@ public class VibratorManagerServiceTest {
return predicateResult;
}
+ private void stopAutoDispatcherAndDispatchAll() {
+ // Stop auto-dispatcher thread and wait for it to finish processing any messages.
+ mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+ // Dispatch any pending message left.
+ mTestLooper.dispatchAll();
+ }
+
private void grantPermission(String permission) {
when(mContextSpy.checkCallingOrSelfPermission(permission))
.thenReturn(PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 946e1eaa5666..3f3476716831 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -26,6 +26,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
@@ -49,6 +50,7 @@ public final class FakeVibratorControllerProvider {
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
private final Map<Long, List<VibrationEffect.VendorEffect>> mVendorEffects = new TreeMap<>();
+ private final Map<Long, List<PwlePoint>> mEffectPwlePoints = new TreeMap<>();
private final Map<Long, List<Integer>> mBraking = new HashMap<>();
private final List<Float> mAmplitudes = new ArrayList<>();
private final List<Boolean> mExternalControlStates = new ArrayList<>();
@@ -76,7 +78,11 @@ public final class FakeVibratorControllerProvider {
private float mFrequencyResolution = Float.NaN;
private float mQFactor = Float.NaN;
private float[] mMaxAmplitudes;
+
+ private float[] mFrequenciesHz;
+ private float[] mOutputAccelerationsGs;
private long mVendorEffectDuration = EFFECT_DURATION;
+ private long mPrimitiveDuration = EFFECT_DURATION;
void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment);
@@ -86,6 +92,10 @@ public final class FakeVibratorControllerProvider {
mVendorEffects.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(vendorEffect);
}
+ void recordEffectPwlePoint(long vibrationId, PwlePoint pwlePoint) {
+ mEffectPwlePoints.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(pwlePoint);
+ }
+
void recordBraking(long vibrationId, int braking) {
mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
}
@@ -168,7 +178,7 @@ public final class FakeVibratorControllerProvider {
}
long duration = 0;
for (PrimitiveSegment primitive : primitives) {
- duration += EFFECT_DURATION + primitive.getDelay();
+ duration += mPrimitiveDuration + primitive.getDelay();
recordEffectSegment(vibrationId, primitive);
}
applyLatency(mOnLatency);
@@ -190,6 +200,19 @@ public final class FakeVibratorControllerProvider {
}
@Override
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+ long duration = 0;
+ for (PwlePoint pwlePoint: pwlePoints) {
+ duration += pwlePoint.getTimeMillis();
+ recordEffectPwlePoint(vibrationId, pwlePoint);
+ }
+ applyLatency(mOnLatency);
+ scheduleListener(duration, vibrationId);
+
+ return duration;
+ }
+
+ @Override
public void setExternalControl(boolean enabled) {
mExternalControlStates.add(enabled);
}
@@ -213,13 +236,16 @@ public final class FakeVibratorControllerProvider {
infoBuilder.setSupportedEffects(mSupportedEffects);
if (mSupportedPrimitives != null) {
for (int primitive : mSupportedPrimitives) {
- infoBuilder.setSupportedPrimitive(primitive, EFFECT_DURATION);
+ infoBuilder.setSupportedPrimitive(primitive, (int) mPrimitiveDuration);
}
}
infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
infoBuilder.setQFactor(mQFactor);
- infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ infoBuilder.setFrequencyProfileLegacy(new VibratorInfo.FrequencyProfileLegacy(
mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
+ infoBuilder.setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(mResonantFrequency, mFrequenciesHz,
+ mOutputAccelerationsGs));
infoBuilder.setMaxEnvelopeEffectSize(mMaxEnvelopeEffectSize);
infoBuilder.setMinEnvelopeEffectControlPointDurationMillis(
mMinEnvelopeEffectControlPointDurationMillis);
@@ -360,11 +386,26 @@ public final class FakeVibratorControllerProvider {
mMaxAmplitudes = maxAmplitudes;
}
+ /** Set the list of available frequencies. */
+ public void setFrequenciesHz(float[] frequenciesHz) {
+ mFrequenciesHz = frequenciesHz;
+ }
+
+ /** Set the max output acceleration achievable by the supported frequencies. */
+ public void setOutputAccelerationsGs(float[] outputAccelerationsGs) {
+ mOutputAccelerationsGs = outputAccelerationsGs;
+ }
+
/** Set the duration of vendor effects in fake vibrator hardware. */
public void setVendorEffectDuration(long durationMs) {
mVendorEffectDuration = durationMs;
}
+ /** Set the duration of primitives in fake vibrator hardware. */
+ public void setPrimitiveDuration(long primitiveDuration) {
+ mPrimitiveDuration = primitiveDuration;
+ }
+
/**
* Set the maximum number of envelope effects control points supported in fake vibrator
* hardware.
@@ -446,6 +487,28 @@ public final class FakeVibratorControllerProvider {
return result;
}
+ /** Return list of {@link PwlePoint} played by this controller, in order. */
+ public List<PwlePoint> getEffectPwlePoints(long vibrationId) {
+ if (mEffectPwlePoints.containsKey(vibrationId)) {
+ return new ArrayList<>(mEffectPwlePoints.get(vibrationId));
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * Returns a list of all vibrations' {@link PwlePoint}s, for external-use where vibration
+ * IDs aren't exposed.
+ */
+ public List<PwlePoint> getAllEffectPwlePoints() {
+ // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+ ArrayList<PwlePoint> result = new ArrayList<>();
+ for (List<PwlePoint> subList : mEffectPwlePoints.values()) {
+ result.addAll(subList);
+ }
+ return result;
+ }
+
/** Return list of states set for external control to the fake vibrator hardware. */
public List<Boolean> getExternalControlStates() {
return mExternalControlStates;
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index ff2ce15a7946..a9a1f93e5ab8 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -41,6 +41,8 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
@@ -62,18 +64,23 @@ public class ConversionUtilTest {
final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
| SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
final var data = new byte[] {0x11, 0x22};
- final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
- keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
+ final var keyphrases = new ArrayList<SoundTrigger.KeyphraseRecognitionExtra>(2);
+ keyphrases.add(new SoundTrigger.KeyphraseRecognitionExtra(99,
RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
- new ConfidenceLevel(5000, 80)});
- keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
+ new ConfidenceLevel(5000, 80)}));
+ keyphrases.add(new SoundTrigger.KeyphraseRecognitionExtra(101,
RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
new ConfidenceLevel(7777, 30),
- new ConfidenceLevel(2222, 60)});
+ new ConfidenceLevel(2222, 60)}));
- var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
- keyphrases, data, flags);
+ var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
+ .setCaptureRequested(true)
+ .setMultipleTriggersAllowed(false) // must be false
+ .setKeyphrases(keyphrases)
+ .setData(data)
+ .setAudioCapabilities(flags)
+ .build();
assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index efe1af336ecd..8b861ed553ce 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -79,6 +79,7 @@ public class SoundTriggerMiddlewareLoggingLatencyTest {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG);
Identity identity = new Identity();
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 4bae9e625ddf..132f95bea360 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -29,7 +29,7 @@ java_genrule {
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
"--protolog-class com.android.internal.protolog.ProtoLog " +
- "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+ "--loggroups-class com.android.internal.protolog.WmProtoLogGroups " +
"--loggroups-jar $(location :protolog-groups) " +
// Used for the ProtoLogIntegrationTest, where don't test decoding or writing to file
// so the parameters below are irrelevant.
@@ -71,6 +71,7 @@ android_test {
"CtsSurfaceValidatorLib",
"service-sdksandbox.impl",
"com.android.window.flags.window-aconfig-java",
+ "android.view.inputmethod.flags-aconfig-java",
"flag-junit",
],
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 6e6b70d319ab..5f2f3ed67432 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -17,10 +17,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.wmtests">
- <!-- Uses API introduced in P (28) -->
+ <!-- Uses API introduced in S (31). Using SDK 31+ avoids Google Play Protect popups. -->
<uses-sdk
android:minSdkVersion="1"
- android:targetSdkVersion="28" />
+ android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 197b36623fff..cbbbc731753d 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -13,60 +13,62 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- the key combinations for the following shortcuts must be in sync
with the key combinations sent by the test in ModifierShortcutTests.java -->
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
- <bookmark
- category="android.intent.category.APP_MUSIC"
- shortcut="p" />
- <bookmark
- role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.BROWSER"
- shortcut="b"
- shift="true" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c"
- shift="true" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j"
- shift="true" />
+ androidprv:keycode="KEYCODE_J"
+ androidprv:modifierState="META|SHIFT" />
<!-- The following shortcuts will not be invoked by tests but are here to
provide test coverage of parsing the different types of shortcut. -->
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j" />
+ androidprv:keycode="KEYCODE_J"
+ androidprv:modifierState="META" />
<bookmark
package="com.test2"
class="com.test.BookmarkTest"
- shortcut="d" />
+ androidprv:keycode="KEYCODE_D"
+ androidprv:modifierState="META" />
<!-- It's intended that this package/class will NOT resolve so we test the resolution
@@ -74,6 +76,7 @@
<bookmark
package="com.test3"
class="com.test.BookmarkTest"
- shortcut="f" />
+ androidprv:keycode="KEYCODE_F"
+ androidprv:modifierState="META" />
</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 1c331166d317..038e1357159b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -22,6 +22,7 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS;
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
+import android.platform.test.annotations.DisableFlags;
import android.view.ViewConfiguration;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +40,7 @@ import org.junit.runner.RunWith;
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
+@DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_KEY_GESTURES)
public class CombinationKeyTests extends ShortcutKeyTestBase {
private static final long A11Y_KEY_HOLD_MILLIS = 3500;
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
index 487390d4411d..8b5f68a1e974 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
@@ -71,12 +71,12 @@ public class KeyCombinationManagerTests {
new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
KEYCODE_POWER) {
@Override
- void execute() {
+ public void execute() {
mAction1Triggered.countDown();
}
@Override
- void cancel() {
+ public void cancel() {
}
});
@@ -85,21 +85,21 @@ public class KeyCombinationManagerTests {
new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
KEYCODE_VOLUME_UP) {
@Override
- boolean preCondition() {
+ public boolean preCondition() {
return mPreCondition;
}
@Override
- void execute() {
+ public void execute() {
mAction2Triggered.countDown();
}
@Override
- void cancel() {
+ public void cancel() {
}
@Override
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
return 0;
}
});
@@ -115,12 +115,12 @@ public class KeyCombinationManagerTests {
};
@Override
- void execute() {
+ public void execute() {
mHandler.postDelayed(mAction, SCHEDULE_TIME);
}
@Override
- void cancel() {
+ public void cancel() {
mHandler.removeCallbacks(mAction);
}
});
@@ -235,12 +235,12 @@ public class KeyCombinationManagerTests {
new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
KEYCODE_POWER) {
@Override
- void execute() {
+ public void execute() {
mAction1Triggered.countDown();
}
@Override
- void cancel() {
+ public void cancel() {
}
};
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 3d978e424375..41865b209f01 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -22,16 +22,21 @@ import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECEN
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS;
+import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
+import android.hardware.input.InputSettings;
import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.KeyEvent;
import androidx.test.filters.MediumTest;
+import com.android.hardware.input.Flags;
import com.android.internal.annotations.Keep;
import junit.framework.Assert;
@@ -56,7 +61,73 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
@Keep
- private static Object[][] shortcutTestArguments() {
+ private static Object[][] shortcutTestArgumentsNotMigratedToKeyGestureController() {
+ // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState
+ return new Object[][]{
+ {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ KeyEvent.KEYCODE_HOME, 0},
+ {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+ KeyEvent.KEYCODE_BACK, 0},
+ {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP,
+ KeyEvent.KEYCODE_VOLUME_UP, 0},
+ {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_DOWN, 0},
+ {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE,
+ KeyEvent.KEYCODE_VOLUME_MUTE, 0},
+ {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
+ 0},
+ {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
+ 0},
+ {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER,
+ KeyEvent.KEYCODE_TV_POWER, 0},
+ {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
+ 0},
+ {"SYSTEM_NAVIGATION_UP key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
+ 0},
+ {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
+ 0},
+ {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
+ {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+ {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
+ 0},
+ {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+ {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
+ KeyEvent.KEYCODE_MEDIA_PLAY, 0},
+ {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
+ KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
+ {"MEDIA_PLAY_PAUSE key -> Media Control",
+ new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}};
+ }
+
+ @Keep
+ private static Object[][] shortcutTestArgumentsMigratedToKeyGestureController() {
// testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState
return new Object[][]{
{"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
@@ -64,9 +135,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER,
META_ON},
- {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
- KeyEvent.KEYCODE_HOME, 0},
{"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
KeyEvent.KEYCODE_RECENT_APPS, 0},
@@ -76,9 +144,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
ALT_ON},
- {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyEvent.KEYCODE_BACK, 0},
{"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE,
META_ON},
@@ -138,15 +203,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
- {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
- KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP,
- KeyEvent.KEYCODE_VOLUME_UP, 0},
- {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN,
- KeyEvent.KEYCODE_VOLUME_DOWN, 0},
- {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
- KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE,
- KeyEvent.KEYCODE_VOLUME_MUTE, 0},
{"ALL_APPS key -> Open App Drawer",
new int[]{KeyEvent.KEYCODE_ALL_APPS},
KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
@@ -170,9 +226,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
KeyEvent.KEYCODE_CAPS_LOCK, 0},
- {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
- KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
- 0},
{"Meta + Ctrl + DPAD_UP -> Split screen navigation",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
@@ -194,57 +247,20 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N,
META_ON | CTRL_ON},
- {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
- 0},
- {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER,
- KeyEvent.KEYCODE_TV_POWER, 0},
- {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
- 0},
- {"SYSTEM_NAVIGATION_UP key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
- KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
- 0},
- {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
- KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
- 0},
- {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
- KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
- {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
- KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
- {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
- KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
- 0},
- {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
- KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
- {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
- KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
- KeyEvent.KEYCODE_MEDIA_PLAY, 0},
- {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
- KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
- KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
- {"MEDIA_PLAY_PAUSE key -> Media Control",
- new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
- KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
+ {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ META_ON | CTRL_ON},
{"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
KeyEvent.KEYCODE_B, META_ON},
{"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
KeyEvent.KEYCODE_EXPLORER, 0},
- {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+ {"Meta + P -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_P},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
- KeyEvent.KEYCODE_C, META_ON},
+ KeyEvent.KEYCODE_P, META_ON},
{"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
KeyEvent.KEYCODE_CONTACTS, 0},
@@ -254,15 +270,12 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
KeyEvent.KEYCODE_ENVELOPE, 0},
- {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+ {"Meta + C -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_C},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
- KeyEvent.KEYCODE_K, META_ON},
+ KeyEvent.KEYCODE_C, META_ON},
{"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
KeyEvent.KEYCODE_CALENDAR, 0},
- {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
- KeyEvent.KEYCODE_P, META_ON},
{"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
KeyEvent.KEYCODE_MUSIC, 0},
@@ -275,16 +288,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
KeyEvent.KEYCODE_CALCULATOR, 0},
{"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
- KeyEvent.KEYCODE_M, META_ON},
- {"Meta + S -> Launch Default Messaging App",
- new int[]{META_KEY, KeyEvent.KEYCODE_S},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
- KeyEvent.KEYCODE_S, META_ON},
- {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
- KeyEvent.KEYCODE_DPAD_DOWN,
- META_ON | CTRL_ON}};
+ KeyEvent.KEYCODE_M, META_ON}};
}
@Keep
@@ -296,72 +300,14 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Toggle Notification panel",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_ENTER,
- META_ON},
- {"Long press META + H -> Toggle Notification panel",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_H, META_ON},
{"Long press HOME key -> Launch assistant",
new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Launch assistant",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Long press META + H -> Launch assistant",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
- META_ON},
{"Long press HOME key -> Open App Drawer",
new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Open App Drawer",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Long press META + H -> Open App Drawer",
- new int[]{META_KEY, KeyEvent.KEYCODE_H},
- LONG_PRESS_HOME_ALL_APPS,
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_H, META_ON}};
- }
-
- @Keep
- private static Object[][] doubleTapOnHomeTestArguments() {
- // testName, testKeys, doubleTapOnHomeBehavior, expectedKeyGestureType, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"Double tap HOME -> Open App switcher",
- new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_HOME,
- 0},
- {"Double tap META + ENTER -> Open App switcher",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Double tap META + H -> Open App switcher",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_H,
- META_ON}};
- }
-
- @Keep
- private static Object[][] settingsKeyTestArguments() {
- // testName, testKeys, settingsKeyBehavior, expectedKeyGestureType, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
- SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_SETTINGS, 0}};
+ KeyEvent.KEYCODE_HOME, 0}};
}
@Before
@@ -378,11 +324,22 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideTogglePanel();
mPhoneWindowManager.overrideInjectKeyEvent();
+ mPhoneWindowManager.overrideRoleManager();
+ }
+
+ @Test
+ @Parameters(method = "shortcutTestArgumentsNotMigratedToKeyGestureController")
+ public void testShortcuts_notMigratedToKeyGestureController(String testName,
+ int[] testKeys, @KeyGestureEvent.KeyGestureType int expectedKeyGestureType,
+ int expectedKey, int expectedModifierState) {
+ testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
+ expectedModifierState);
}
@Test
- @Parameters(method = "shortcutTestArguments")
- public void testShortcut(String testName, int[] testKeys,
+ @Parameters(method = "shortcutTestArgumentsMigratedToKeyGestureController")
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testShortcuts_migratedToKeyGestureController(String testName, int[] testKeys,
@KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
int expectedModifierState) {
testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
@@ -402,31 +359,29 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
- @Parameters(method = "doubleTapOnHomeTestArguments")
- public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
- int doubleTapOnHomeBehavior,
- @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
- int expectedModifierState) {
- mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
- sendKeyCombination(testKeys, 0 /* duration */);
- sendKeyCombination(testKeys, 0 /* duration */);
+ public void testDoubleTapOnHomeBehavior_AppSwitchBehavior() {
+ mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(DOUBLE_TAP_HOME_RECENT_SYSTEM_UI);
+ sendKeyCombination(new int[]{KeyEvent.KEYCODE_HOME}, 0 /* duration */);
+ sendKeyCombination(new int[]{KeyEvent.KEYCODE_HOME}, 0 /* duration */);
mPhoneWindowManager.assertKeyGestureCompleted(
- new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
- "Failed while executing " + testName);
+ new int[]{KeyEvent.KEYCODE_HOME}, /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ "Failed while executing Double tap HOME -> Open App switcher");
}
@Test
- @Parameters(method = "settingsKeyTestArguments")
- public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
- @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
- int expectedModifierState) {
- mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
- testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
- expectedModifierState);
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testSettingsKey_ToggleNotificationBehavior() {
+ mPhoneWindowManager.overrideSettingsKeyBehavior(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL);
+ testShortcutInternal("SETTINGS key -> Toggle Notification panel",
+ new int[]{KeyEvent.KEYCODE_SETTINGS},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_SETTINGS, 0);
}
@Test
@EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testBugreportShortcutPress() {
testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
@@ -434,6 +389,47 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
META_ON | CTRL_ON);
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testToggleTalkbackPress() {
+ testShortcutInternal("Meta + Alt + T -> Toggle talkback",
+ new int[]{META_KEY, ALT_KEY, KeyEvent.KEYCODE_T},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
+ KeyEvent.KEYCODE_T,
+ META_ON | ALT_ON);
+ }
+
+ @Test
+ @EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS})
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testKeyboardAccessibilityToggleShortcutPress() {
+ testShortcutInternal("Meta + Alt + 3 -> Toggle Bounce Keys",
+ new int[]{META_KEY, ALT_KEY, KeyEvent.KEYCODE_3},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
+ KeyEvent.KEYCODE_3,
+ META_ON | ALT_ON);
+ testShortcutInternal("Meta + Alt + 4 -> Toggle Mouse Keys",
+ new int[]{META_KEY, ALT_KEY, KeyEvent.KEYCODE_4},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+ KeyEvent.KEYCODE_4,
+ META_ON | ALT_ON);
+ testShortcutInternal("Meta + Alt + 5 -> Toggle Sticky Keys",
+ new int[]{META_KEY, ALT_KEY, KeyEvent.KEYCODE_5},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
+ KeyEvent.KEYCODE_5,
+ META_ON | ALT_ON);
+ testShortcutInternal("Meta + Alt + 6 -> Toggle Slow Keys",
+ new int[]{META_KEY, ALT_KEY, KeyEvent.KEYCODE_6},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
+ KeyEvent.KEYCODE_6,
+ META_ON | ALT_ON);
+ }
+
private void testShortcutInternal(String testName, int[] testKeys,
@KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
int expectedModifierState) {
@@ -465,6 +461,14 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
+ public void testKeyGestureLaunchVoiceAssistant() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT));
+ mPhoneWindowManager.assertSearchManagerLaunchAssist();
+ }
+
+ @Test
public void testKeyGestureGoHome() {
Assert.assertTrue(
sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME));
@@ -599,4 +603,209 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH));
mPhoneWindowManager.assertLaunchSearch();
}
+
+ @Test
+ public void testKeyGestureScreenshotChord() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ mPhoneWindowManager.moveTimeForward(500);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ @Test
+ public void testKeyGestureScreenshotChordCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ mPhoneWindowManager.assertTakeScreenshotNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityShortcutChord() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.moveTimeForward(5000);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityShortcutChordCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureRingerToggleChord() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ mPhoneWindowManager.moveTimeForward(500);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ mPhoneWindowManager.assertVolumeMute();
+ }
+
+ @Test
+ public void testKeyGestureRingerToggleChordCancelled() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ mPhoneWindowManager.assertVolumeNotMuted();
+ }
+
+ @Test
+ public void testKeyGestureGlobalAction() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ mPhoneWindowManager.moveTimeForward(500);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ mPhoneWindowManager.assertShowGlobalActionsCalled();
+ }
+
+ @Test
+ public void testKeyGestureGlobalActionCancelled() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ mPhoneWindowManager.assertShowGlobalActionsNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityTvShortcutChord() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.moveTimeForward(5000);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityTvShortcutChordCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureTvTriggerBugReport() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ mPhoneWindowManager.moveTimeForward(1000);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ mPhoneWindowManager.assertBugReportTakenForTv();
+ }
+
+ @Test
+ public void testKeyGestureTvTriggerBugReportCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ mPhoneWindowManager.assertBugReportNotTakenForTv();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityShortcut() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT));
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ @Test
+ public void testKeyGestureCloseAllDialogs() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS));
+ mPhoneWindowManager.assertCloseAllDialogs();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void testKeyGestureToggleTalkback() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ mPhoneWindowManager.assertTalkBack(true);
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ mPhoneWindowManager.assertTalkBack(false);
+ }
+
+ @Test
+ @EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG})
+ public void testKeyGestureToggleStickyKeys() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS));
+ Assert.assertTrue(InputSettings.isAccessibilityStickyKeysEnabled(mContext));
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS));
+ Assert.assertFalse(InputSettings.isAccessibilityStickyKeysEnabled(mContext));
+ }
+
+ @Test
+ @EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG})
+ public void testKeyGestureToggleSlowKeys() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS));
+ Assert.assertTrue(InputSettings.isAccessibilitySlowKeysEnabled(mContext));
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS));
+ Assert.assertFalse(InputSettings.isAccessibilitySlowKeysEnabled(mContext));
+ }
+
+ @Test
+ @EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS})
+ public void testKeyGestureToggleMouseKeys() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS));
+ Assert.assertTrue(InputSettings.isAccessibilityMouseKeysEnabled(mContext));
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS));
+ Assert.assertFalse(InputSettings.isAccessibilityMouseKeysEnabled(mContext));
+ }
+
+ @Test
+ @EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG})
+ public void testKeyGestureToggleBounceKeys() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS));
+ Assert.assertTrue(InputSettings.isAccessibilityBounceKeysEnabled(mContext));
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS));
+ Assert.assertFalse(InputSettings.isAccessibilityBounceKeysEnabled(mContext));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 0575d98b65ec..82a5add407f4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -116,6 +116,7 @@ public class ModifierShortcutManagerTests {
mModifierShortcutManager = new ModifierShortcutManager(
mContext, mHandler, UserHandle.SYSTEM);
+ mModifierShortcutManager.onSystemReady();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 43171f847818..35b077e30f12 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -26,7 +26,6 @@ import static android.view.KeyEvent.KEYCODE_E;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.KeyEvent.KEYCODE_H;
import static android.view.KeyEvent.KEYCODE_J;
-import static android.view.KeyEvent.KEYCODE_K;
import static android.view.KeyEvent.KEYCODE_M;
import static android.view.KeyEvent.KEYCODE_META_LEFT;
import static android.view.KeyEvent.KEYCODE_N;
@@ -43,6 +42,8 @@ import static android.view.KeyEvent.KEYCODE_Z;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -65,14 +66,12 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
// These shortcuts should align with those defined in
// services/tests/wmtests/res/xml/bookmarks.xml
INTENT_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR);
- INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
+ INTENT_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_CONTACTS);
INTENT_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
- INTENT_SHORTCUTS.append(KEYCODE_K, Intent.CATEGORY_APP_CALENDAR);
+ INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CALENDAR);
INTENT_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS);
- INTENT_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
ROLE_SHORTCUTS.append(KEYCODE_B, RoleManager.ROLE_BROWSER);
- ROLE_SHORTCUTS.append(KEYCODE_S, RoleManager.ROLE_SMS);
}
private static final int ANY_DISPLAY_ID = 123;
@@ -85,7 +84,8 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* Test meta+ shortcuts defined in bookmarks.xml.
*/
@Test
- public void testMetaShortcuts() {
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testMetaShortcuts_withoutKeyGestureEventHandling() {
for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
final int keyCode = INTENT_SHORTCUTS.keyAt(i);
final String category = INTENT_SHORTCUTS.valueAt(i);
@@ -106,7 +106,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_B}, 0);
mPhoneWindowManager.assertLaunchRole(RoleManager.ROLE_BROWSER);
- sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_C}, 0);
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_P}, 0);
mPhoneWindowManager.assertLaunchCategory(Intent.CATEGORY_APP_CONTACTS);
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_J}, 0);
@@ -115,10 +115,54 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
}
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testMetaShortcuts_withKeyGestureEventHandling() {
+ for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
+ final String category = INTENT_SHORTCUTS.valueAt(i);
+ mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(category))
+ .build()
+ );
+ mPhoneWindowManager.assertLaunchCategory(category);
+ }
+
+ mPhoneWindowManager.overrideRoleManager();
+ for (int i = 0; i < ROLE_SHORTCUTS.size(); i++) {
+ final String role = ROLE_SHORTCUTS.valueAt(i);
+
+ mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForRole(role))
+ .build()
+ );
+ mPhoneWindowManager.assertLaunchRole(role);
+ }
+
+ mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .setAppLaunchData(
+ new AppLaunchData.ComponentData("com.test",
+ "com.test.BookmarkTest"))
+ .build()
+ );
+ mPhoneWindowManager.assertActivityTargetLaunched(
+ new ComponentName("com.test", "com.test.BookmarkTest"));
+
+ }
+
/**
* ALT + TAB to show recent apps.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testAltTab() {
mPhoneWindowManager.overrideStatusBarManagerInternal();
sendKeyCombination(new int[]{KEYCODE_ALT_LEFT, KEYCODE_TAB}, 0);
@@ -129,6 +173,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* CTRL + SPACE to switch keyboard layout.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testCtrlSpace() {
sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0,
ANY_DISPLAY_ID);
@@ -139,6 +184,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* CTRL + SHIFT + SPACE to switch keyboard layout backwards.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testCtrlShiftSpace() {
sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE},
/* duration= */ 0, ANY_DISPLAY_ID);
@@ -149,6 +195,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* CTRL + ALT + Z to enable accessibility service.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testCtrlAltZ() {
sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_ALT_LEFT, KEYCODE_Z}, 0);
mPhoneWindowManager.assertAccessibilityKeychordCalled();
@@ -158,6 +205,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META + CTRL+ S to take screenshot.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaCtrlS() {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_S}, 0);
mPhoneWindowManager.assertTakeScreenshotCalled();
@@ -167,6 +215,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META + N to expand notification panel.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaN() throws RemoteException {
mPhoneWindowManager.overrideTogglePanel();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0);
@@ -177,6 +226,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META + SLASH to toggle shortcuts menu.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaSlash() {
mPhoneWindowManager.overrideStatusBarManagerInternal();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SLASH}, 0);
@@ -187,6 +237,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META + ALT to toggle Cap Lock.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaAlt() {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
mPhoneWindowManager.assertToggleCapsLock();
@@ -196,6 +247,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META + H to go to homescreen
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaH() {
mPhoneWindowManager.overrideLaunchHome();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_H}, 0);
@@ -206,6 +258,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META + ENTER to go to homescreen
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaEnter() {
mPhoneWindowManager.overrideLaunchHome();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0);
@@ -216,6 +269,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* Sends a KEYCODE_BRIGHTNESS_DOWN event and validates the brightness is decreased as expected;
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testKeyCodeBrightnessDown() {
float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f};
float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f};
@@ -231,29 +285,18 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testTakeScreenshot_flagEnabled() {
- mSetFlagsRule.enableFlags(com.android.hardware.input.Flags
- .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
mPhoneWindowManager.assertTakeScreenshotCalled();
}
/**
- * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
- */
- @Test
- public void testTakeScreenshot_flagDisabled() {
- mSetFlagsRule.disableFlags(com.android.hardware.input.Flags
- .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
- sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
- mPhoneWindowManager.assertTakeScreenshotNotCalled();
- }
-
- /**
* META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled.
*/
@Test
@EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testTakeBugReport_flagEnabled() throws RemoteException {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
mPhoneWindowManager.assertTakeBugreport(true);
@@ -263,7 +306,8 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* META+CTRL+BACKSPACE for taking a bugreport does nothing when the flag is disabledd.
*/
@Test
- @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags({com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER})
public void testTakeBugReport_flagDisabled() throws RemoteException {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
mPhoneWindowManager.assertTakeBugreport(false);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 50b7db434267..9e47a008592c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -241,6 +241,13 @@ class ShortcutKeyTestBase {
KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
}
+ boolean sendKeyGestureEventCancel(int gestureType) {
+ return mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).setFlags(
+ KeyGestureEvent.FLAG_CANCELLED).build());
+ }
+
boolean sendKeyGestureEventComplete(int gestureType, int modifierState) {
return mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType(
@@ -276,7 +283,7 @@ class ShortcutKeyTestBase {
if ((actions & ACTION_PASS_TO_USER) != 0) {
if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) {
if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) {
- mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
+ mPhoneWindowManager.interceptUnhandledKey(keyEvent);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 9b92ff45952b..3ea3235df0f4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -23,6 +23,7 @@ import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS;
import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP;
import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
@@ -32,6 +33,7 @@ import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
+import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
import android.provider.Settings;
import android.view.Display;
@@ -236,6 +238,19 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
}
@Test
+ public void stemDoubleKey_behaviorIsLaunchFitness_gestureEventFired() {
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertKeyGestureEventSentToKeyGestureController(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS);
+ }
+
+ @Test
public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y()
throws RemoteException {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
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 98401b33840f..9db76d47fed7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -88,6 +88,7 @@ import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
import android.view.Display;
import android.view.InputEvent;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillManagerInternal;
@@ -197,7 +198,7 @@ class TestPhoneWindowManager {
}
@Override
- boolean toggleTalkback(int currentUserId) {
+ boolean toggleTalkback(int currentUserId, ShortcutSource source) {
mIsTalkBackEnabled = !mIsTalkBackEnabled;
return mIsTalkBackEnabled;
}
@@ -270,11 +271,15 @@ class TestPhoneWindowManager {
// Return mocked services: LocalServices.getService
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
+ .mockStatic(KeyCharacterMap.class)
.strictness(Strictness.LENIENT)
.startMocking();
mPhoneWindowManager = spy(new PhoneWindowManager());
+ KeyCharacterMap virtualKcm = mContext.getSystemService(InputManager.class)
+ .getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD).getKeyCharacterMap();
+ doReturn(virtualKcm).when(() -> KeyCharacterMap.load(anyInt()));
doReturn(mWindowManagerInternal).when(
() -> LocalServices.getService(eq(WindowManagerInternal.class)));
doReturn(mActivityManagerInternal).when(
@@ -418,8 +423,8 @@ class TestPhoneWindowManager {
mKeyEventPolicyFlags);
}
- void dispatchUnhandledKey(KeyEvent event) {
- mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
+ void interceptUnhandledKey(KeyEvent event) {
+ mPhoneWindowManager.interceptUnhandledKey(event, mInputToken);
}
boolean sendKeyGestureEvent(KeyGestureEvent event) {
@@ -518,12 +523,12 @@ class TestPhoneWindowManager {
}
void prepareBrightnessDecrease(float currentBrightness) {
- doReturn(0.0f).when(mPowerManager)
- .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- doReturn(1.0f).when(mPowerManager)
- .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+ doReturn(0.0f).when(mPowerManager).getBrightnessConstraint(
+ DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+ doReturn(1.0f).when(mPowerManager).getBrightnessConstraint(
+ DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
doReturn(currentBrightness).when(mDisplayManager)
- .getBrightness(0);
+ .getBrightness(DEFAULT_DISPLAY);
}
void verifyNewBrightness(float newBrightness) {
@@ -657,16 +662,36 @@ class TestPhoneWindowManager {
verify(mPowerManager).userActivity(anyLong(), anyBoolean());
}
+ void assertShowGlobalActionsNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mGlobalActions, never()).showDialog(anyBoolean(), anyBoolean());
+ verify(mPowerManager, never()).userActivity(anyLong(), anyBoolean());
+ }
+
void assertVolumeMute() {
mTestLooper.dispatchAll();
verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush"));
}
+ void assertVolumeNotMuted() {
+ mTestLooper.dispatchAll();
+ verify(mAudioManagerInternal, never()).silenceRingerModeInternal(any());
+ }
+
void assertAccessibilityKeychordCalled() {
mTestLooper.dispatchAll();
verify(mAccessibilityShortcutController).performAccessibilityShortcut();
}
+ void assertAccessibilityKeychordNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mAccessibilityShortcutController, never()).performAccessibilityShortcut();
+ }
+
+ void assertCloseAllDialogs() {
+ verify(mContext).closeSystemDialogs();
+ }
+
void assertDreamRequest() {
mTestLooper.dispatchAll();
verify(mDreamManagerInternal).requestDream();
@@ -679,8 +704,8 @@ class TestPhoneWindowManager {
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
- verify(mWindowWakeUpPolicy)
- .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
+ verify(mWindowWakeUpPolicy).wakeUpFromKey(
+ eq(DEFAULT_DISPLAY), anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
@@ -802,13 +827,23 @@ class TestPhoneWindowManager {
void assertTakeBugreport(boolean wasCalled) throws RemoteException {
mTestLooper.dispatchAll();
if (wasCalled) {
- verify(mActivityManagerService).requestInteractiveBugReport();
+ verify(mActivityManagerService).launchBugReportHandlerApp();
} else {
- verify(mActivityManagerService, never()).requestInteractiveBugReport();
+ verify(mActivityManagerService, never()).launchBugReportHandlerApp();
}
}
+ void assertBugReportTakenForTv() {
+ mTestLooper.dispatchAll();
+ verify(mPhoneWindowManager).requestBugreportForTv();
+ }
+
+ void assertBugReportNotTakenForTv() {
+ mTestLooper.dispatchAll();
+ verify(mPhoneWindowManager, never()).requestBugreportForTv();
+ }
+
void assertTogglePanel() throws RemoteException {
mTestLooper.dispatchAll();
verify(mPhoneWindowManager.mStatusBarService).togglePanel();
@@ -889,4 +924,9 @@ class TestPhoneWindowManager {
mTestLooper.dispatchAll();
Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
}
+
+ void assertKeyGestureEventSentToKeyGestureController(int gestureType) {
+ verify(mInputManagerInternal)
+ .handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 7322e5a3b681..3ca352cfa60d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -22,6 +22,7 @@ import static android.os.PowerManager.WAKE_REASON_GESTURE;
import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.KeyEvent.KEYCODE_HOME;
@@ -35,6 +36,7 @@ import static com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraL
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+import static com.android.server.power.feature.flags.Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -43,6 +45,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -52,6 +55,8 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
@@ -125,6 +130,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -136,7 +142,8 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
verifyNoPowerManagerWakeUp();
@@ -144,12 +151,14 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 300, SOURCE_ROTARY_ENCODER, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -161,7 +170,7 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
verifyNoPowerManagerWakeUp();
@@ -169,7 +178,8 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, 300, KEYCODE_STEM_PRIMARY, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
}
@@ -186,7 +196,7 @@ public final class WindowWakeUpPolicyTests {
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
}
@@ -201,11 +211,13 @@ public final class WindowWakeUpPolicyTests {
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
setDefaultDisplayState(Display.STATE_ON);
@@ -213,30 +225,69 @@ public final class WindowWakeUpPolicyTests {
setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
- mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
+ mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(), 200L, SOURCE_TOUCHSCREEN, true);
verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromMotion() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ () -> mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(),
+ mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
config_allowTheaterModeWakeFromMotion,
WAKE_REASON_WAKE_MOTION,
"android.policy:MOTION");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchDisabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that power is woken up and display isn't woken up individually
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(
+ anyLong(), eq(WAKE_REASON_WAKE_MOTION), eq("android.policy:MOTION"));
+ verify(mPowerManager, never()).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_nonPowerKey() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_HOME, true),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_WAKE_KEY,
"android.policy:KEY");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_powerKey() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -245,7 +296,8 @@ public final class WindowWakeUpPolicyTests {
// Test with power key
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, true),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
@@ -254,13 +306,31 @@ public final class WindowWakeUpPolicyTests {
// even if the power-key specific theater mode config is disabled.
setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, false),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromKey_invalidDisplay_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = Display.INVALID_DISPLAY;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromKey(
+ displayId, mClock.uptimeMillis(), KEYCODE_POWER, /* isDown= */ false);
+
+ // Verify that default display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_POWER_BUTTON),
+ eq("android.policy:POWER"), eq(DEFAULT_DISPLAY));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromLid() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromLid(),
@@ -270,6 +340,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromWakeGesture() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromWakeGesture(),
@@ -279,6 +350,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testwakeUpFromCameraCover() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
@@ -288,6 +360,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromPowerKeyCameraGesture() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index d4ba3b25178d..9e7575f1c644 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -538,7 +538,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
public void testConsecutiveLaunchNewTask() {
final IBinder launchCookie = mock(IBinder.class);
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
- mTrampolineActivity.noDisplay = true;
+ mTrampolineActivity.setIsNoDisplay(true);
mTrampolineActivity.mLaunchCookie = launchCookie;
mTrampolineActivity.mLaunchRootTask = launchRootTask;
onActivityLaunched(mTrampolineActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 5787780cef46..4cd75d5ba074 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -308,6 +308,8 @@ public class ActivityOptionsTest {
// KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER
+ case "android.activity.allowPassThroughOnTouchOutside":
+ // KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE
// Existing keys
break;
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 577b02a4ff7a..d4a921c5f00a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -139,7 +139,6 @@ import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.provider.DeviceConfig;
import android.util.MutableBoolean;
import android.view.DisplayInfo;
@@ -2662,23 +2661,27 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT)
public void testSetOrientation_restrictedByTargetSdk() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ makeDisplayLargeScreen(mDisplayContent);
+
assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false);
assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
// Blanket application, also ignoring Target SDK
- mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+ mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, false);
}
private void assertSetOrientation(int targetSdk, int category, boolean expectRotate) {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- activity.mTargetSdk = targetSdk;
+ final String packageName = targetSdk <= Build.VERSION_CODES.VANILLA_ICE_CREAM
+ ? mContext.getPackageName() // WmTests uses legacy sdk.
+ : null; // Simulate CUR_DEVELOPMENT by invalid package (see PlatformCompat).
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setComponent(getUniqueComponentName(packageName)).build();
activity.info.applicationInfo.category = category;
- activity.setVisible(true);
-
// Assert orientation is unspecified to start.
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
@@ -3136,11 +3139,13 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testOnStartingWindowDrawn() {
+ // Skip unnecessary resume top.
+ mSupervisor.beginDeferResume();
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
// The task-has-been-visible should not affect the decision of making transition ready.
activity.getTask().setHasBeenVisible(true);
activity.detachFromProcess();
- activity.mStartingData = mock(StartingData.class);
+ activity.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
registerTestTransitionPlayer();
final Transition transition = activity.mTransitionController.requestTransitionIfNeeded(
WindowManager.TRANSIT_OPEN, 0 /* flags */, null /* trigger */, mDisplayContent);
@@ -3148,7 +3153,11 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(activity.mStartingData.mIsDisplayed);
// The transition can be ready by the starting window of a visible-requested activity
// without a running process.
- assertTrue(transition.allReady());
+ if (!transition.allReady()) {
+ // Print unsatisfied conditions.
+ transition.onReadyTimeout();
+ Assert.fail(transition + " must be ready by onStartingWindowDrawn");
+ }
// If other event makes the transition unready, the reentrant of onStartingWindowDrawn
// should not replace the readiness again.
@@ -3214,23 +3223,32 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testSetVisibility_visibleToInvisible() {
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true).build();
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+ final ActivityRecord activity = mAppWindow.mActivityRecord;
+ makeWindowVisibleAndDrawn(mAppWindow);
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ assertTrue(mAppWindow.isDrawn());
+ assertFalse(mAppWindow.setReportResizeHints());
// Request the activity to be invisible. Since the visibility changes, app transition
// animation should be applied on this activity.
- mDisplayContent.prepareAppTransition(0);
+ activity.mTransitionController.requestCloseTransitionIfNeeded(activity);
activity.setVisibility(false);
assertTrue(activity.isVisible());
assertFalse(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertTrue(activity.mDisplayContent.mClosingApps.contains(activity));
+
+ player.start();
+ mSetFlagsRule.enableFlags(Flags.FLAG_RESET_DRAW_STATE_ON_CLIENT_INVISIBLE);
+ // ActivityRecord#commitVisibility(false) -> WindowState#sendAppVisibilityToClients().
+ player.finish();
+ assertFalse(activity.isVisible());
+ assertFalse("Reset draw state after committing invisible", mAppWindow.isDrawn());
+ assertTrue("Set pending redraw hint", mAppWindow.setReportResizeHints());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index cb17f35f64d7..63dafcd0b5a8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -197,7 +197,7 @@ public class ActivityRefresherTests extends WindowTestsBase {
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
- .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+ .scheduleTransactionItems(mActivity.app.getThread(),
refreshCallbackItem, resumeActivityItem);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 2a53df9f8353..a7fc10f2fcc5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -17,30 +17,23 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SnapshotPersistQueue.MAX_STORE_QUEUE_DEPTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import android.content.ComponentName;
-import android.graphics.ColorSpace;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
-import android.view.Surface;
import android.window.TaskSnapshot;
import androidx.test.filters.SmallTest;
@@ -61,14 +54,20 @@ import java.util.Arrays;
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class ActivitySnapshotControllerTests extends WindowTestsBase {
+public class ActivitySnapshotControllerTests extends TaskSnapshotPersisterTestBase {
private ActivitySnapshotController mActivitySnapshotController;
+ public ActivitySnapshotControllerTests() {
+ super(0.8f /* highResScale */, 0.5f /* lowResScale */);
+ }
+
+ @Override
@Before
- public void setUp() throws Exception {
- spyOn(mWm.mSnapshotController.mActivitySnapshotController);
- mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController;
+ public void setUp() {
+ super.setUp();
+ mActivitySnapshotController = new ActivitySnapshotController(mWm, mSnapshotPersistQueue);
+ spyOn(mActivitySnapshotController);
doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots();
mActivitySnapshotController.resetTmpFields();
}
@@ -92,12 +91,11 @@ public class ActivitySnapshotControllerTests extends WindowTestsBase {
assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size());
mActivitySnapshotController.resetTmpFields();
- // simulate three activity
+ // simulate three activity, the bottom activity won't participate in transition
final WindowState belowClose = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
"belowClose");
belowClose.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
- windows.add(belowClose.mActivityRecord);
mActivitySnapshotController.handleTransitionFinish(windows);
assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
assertEquals(belowClose.mActivityRecord,
@@ -249,15 +247,28 @@ public class ActivitySnapshotControllerTests extends WindowTestsBase {
assertEquals(taskSnapshot, mActivitySnapshotController.getSnapshot(activities));
}
- private TaskSnapshot createSnapshot() {
- HardwareBuffer buffer = mock(HardwareBuffer.class);
- doReturn(100).when(buffer).getWidth();
- doReturn(100).when(buffer).getHeight();
- return new TaskSnapshot(1, 0 /* captureTime */, new ComponentName("", ""), buffer,
- ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
- Surface.ROTATION_0, new Point(100, 100), new Rect() /* contentInsets */,
- new Rect() /* letterboxInsets*/, false /* isLowResolution */,
- true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
- false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
+ /**
+ * Verifies that activity snapshot is skipped if the persister queue has too many pending write
+ * items.
+ */
+ @Test
+ public void testSkipRecordActivity() {
+ doReturn(createSnapshot()).when(mActivitySnapshotController).recordSnapshotInner(any());
+ final Task task = createTask(mDisplayContent);
+
+ mSnapshotPersistQueue.setPaused(true);
+ final ArrayList<ActivityRecord> tmpList = new ArrayList<>();
+ for (int i = 0; i < MAX_STORE_QUEUE_DEPTH; ++i) {
+ tmpList.clear();
+ final ActivityRecord activity = createActivityRecord(task);
+ tmpList.add(activity);
+ mActivitySnapshotController.recordSnapshot(tmpList);
+ assertNotNull(mActivitySnapshotController.findSavedFile(activity));
+ }
+ tmpList.clear();
+ final ActivityRecord activity = createActivityRecord(task);
+ tmpList.add(activity);
+ mActivitySnapshotController.recordSnapshot(tmpList);
+ assertNull(mActivitySnapshotController.findSavedFile(activity));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d0080d29f82b..94a40020cfc8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -345,7 +345,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setResultTo(resultTo)
.setRequestCode(requestCode)
.setReason("testLaunchActivityPermissionDenied")
- .setActivityOptions(new SafeActivityOptions(options))
+ .setActivityOptions(new SafeActivityOptions(
+ options, Binder.getCallingPid(), Binder.getCallingUid()))
.execute();
verify(options, times(1)).abort();
}
@@ -469,7 +470,8 @@ public class ActivityStarterTests extends WindowTestsBase {
optionStarter
.setReason("testCreateTaskLayout")
.setActivityInfo(info)
- .setActivityOptions(new SafeActivityOptions(options))
+ .setActivityOptions(new SafeActivityOptions(
+ options, Binder.getCallingPid(), Binder.getCallingUid()))
.execute();
// verify that values are passed to the modifier. Values are passed thrice -- two for
@@ -775,7 +777,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setCaller(caller)
.setCallingUid(UNIMPORTANT_UID)
.setRealCallingUid(UNIMPORTANT_UID2)
- .setActivityOptions(new SafeActivityOptions(options))
+ .setActivityOptions(new SafeActivityOptions(
+ options, Binder.getCallingPid(), Binder.getCallingUid()))
.setOutActivity(outActivity);
final int result = starter.setReason("testPinnedSingleInstanceAborted").execute();
@@ -811,7 +814,8 @@ public class ActivityStarterTests extends WindowTestsBase {
prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */)
.setReason("testAdjustLaunchTargetWithAdjacentTask")
.setIntent(activity.intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Verify the activity will be launched into the original parent
@@ -883,7 +887,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
final int result = starter.setReason("testDeliverIntentToTopActivityOfNonTopDisplay")
.setIntent(topActivityOnSecondaryDisplay.intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Ensure result is delivering intent to top.
@@ -928,7 +933,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
final int result = starter.setReason("testBringTaskToFrontOnSecondaryDisplay")
.setIntent(singleTaskActivity.intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Ensure result is moving existing task to front.
@@ -974,7 +980,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
final int result = starter.setReason("testStartActivityOnVirtualDisplay")
.setIntent(topActivityOnSecondaryDisplay.intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Ensure result is delivering intent to top.
@@ -1017,7 +1024,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
final int result = starter.setReason("testStartOptedOutActivityOnVirtualDisplay")
.setIntent(topActivityOnSecondaryDisplay.intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Ensure result is canceled.
@@ -1099,7 +1107,8 @@ public class ActivityStarterTests extends WindowTestsBase {
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
starter.setReason("testReparentTopFocusedActivityToSecondaryDisplay")
.setIntent(topActivity.intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Ensure the activity is moved to secondary display.
@@ -1121,7 +1130,8 @@ public class ActivityStarterTests extends WindowTestsBase {
options.setFreezeRecentTasksReordering();
starter.setReason("testFreezeTaskListActivityOption")
- .setActivityOptions(new SafeActivityOptions(options))
+ .setActivityOptions(new SafeActivityOptions(options,
+ Binder.getCallingPid(), Binder.getCallingUid()))
.execute();
verify(recentTasks, times(1)).setFreezeTaskListReordering();
@@ -1143,7 +1153,8 @@ public class ActivityStarterTests extends WindowTestsBase {
options.setFreezeRecentTasksReordering();
starter.setReason("testFreezeTaskListActivityOptionFailedStart")
- .setActivityOptions(new SafeActivityOptions(options))
+ .setActivityOptions(new SafeActivityOptions(options,
+ Binder.getCallingPid(), Binder.getCallingUid()))
.execute();
// Simulate a failed start
@@ -1217,7 +1228,7 @@ public class ActivityStarterTests extends WindowTestsBase {
@Test
public void testRecycleTaskWakeUpWhenDreaming() {
- doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyInt(), anyString());
doReturn(true).when(mWm.mAtmService).isDreaming();
final ActivityStarter starter = prepareStarter(0 /* flags */);
final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -1232,7 +1243,7 @@ public class ActivityStarterTests extends WindowTestsBase {
assertTrue(target.currentLaunchCanTurnScreenOn());
// In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
// will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
- verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ verify(mWm.mAtmService.mTaskSupervisor).wakeUp(eq(target.getDisplayId()), anyString());
}
@Test
@@ -1244,7 +1255,8 @@ public class ActivityStarterTests extends WindowTestsBase {
final ActivityRecord[] outActivity = new ActivityRecord[1];
// Activity must not land on split-screen task if currently not in split-screen mode.
- starter.setActivityOptions(options.toBundle())
+ starter.setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.setReason("testTargetTaskInSplitScreen")
.setOutActivity(outActivity).execute();
assertThat(outActivity[0].inMultiWindowMode()).isFalse();
@@ -1261,6 +1273,34 @@ public class ActivityStarterTests extends WindowTestsBase {
}
@Test
+ public void testLaunchAdjacentDisabled() {
+ final ActivityStarter starter =
+ prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
+ final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ final ActivityRecord[] outActivity = new ActivityRecord[1];
+
+ // Activity must not land on split-screen task if currently not in split-screen mode.
+ starter.setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
+ .setReason("testLaunchAdjacentDisabled")
+ .setOutActivity(outActivity).execute();
+ assertThat(outActivity[0].inMultiWindowMode()).isFalse();
+
+ // Move activity to split-screen-primary task and make sure it has the focus.
+ TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
+ top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM);
+ top.getRootTask().moveToFront("testLaunchAdjacentDisabled");
+ top.getRootTask().setLaunchAdjacentDisabled(true);
+
+ // Ensure activity does not launch into split-screen-secondary when launch adjacent is
+ // disabled
+ startActivityInner(starter, outActivity[0], top, options, null /* inTask */,
+ null /* taskFragment*/);
+ assertThat(outActivity[0].isDescendantOf(splitOrg.mSecondary)).isFalse();
+ }
+
+ @Test
public void testTransientLaunchWithKeyguard() {
final ActivityStarter starter = prepareStarter(0 /* flags */);
final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -1270,7 +1310,8 @@ public class ActivityStarterTests extends WindowTestsBase {
doReturn(true).when(keyguard).isKeyguardOccluded(anyInt());
registerTestTransitionPlayer();
starter.setReason("testTransientLaunchWithKeyguard")
- .setActivityOptions(ActivityOptions.makeBasic().setTransientLaunch().toBundle())
+ .setActivityOptions(ActivityOptions.makeBasic().setTransientLaunch().toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.setIntent(target.intent)
.execute();
final TransitionController controller = mRootWindowContainer.mTransitionController;
@@ -1447,7 +1488,8 @@ public class ActivityStarterTests extends WindowTestsBase {
intent.setComponent(ActivityBuilder.getDefaultComponent());
starter.setReason("testLaunchCookie_newTask")
.setIntent(intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Verify the cookie is set
@@ -1459,7 +1501,8 @@ public class ActivityStarterTests extends WindowTestsBase {
newOptions.setLaunchCookie(newCookie);
starter.setReason("testLaunchCookie_existingTask")
.setIntent(intent)
- .setActivityOptions(newOptions.toBundle())
+ .setActivityOptions(newOptions.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Verify the cookie is updated
@@ -1485,7 +1528,8 @@ public class ActivityStarterTests extends WindowTestsBase {
final ActivityOptions options = ActivityOptions.makeRemoteAnimation(adaptor);
starter.setReason("testRemoteAnimation_existingTask")
.setIntent(intent)
- .setActivityOptions(options.toBundle())
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
.execute();
// Verify the remote animation is updated.
@@ -1598,16 +1642,18 @@ public class ActivityStarterTests extends WindowTestsBase {
assertTrue(activityTop.isVisible());
assertTrue(activityTop.isVisibleRequested());
+ final ActivityRecord[] outActivity = new ActivityRecord[1];
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
| FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
starter.mStartActivity = activityBot;
task.inRecents = true;
- starter.setInTask(task);
+ starter.setInTask(task).setOutActivity(outActivity);
starter.getIntent().setComponent(activityBot.mActivityComponent);
final int result = starter.setReason("testRecordActivityMovement").execute();
assertEquals(START_DELIVERED_TO_TOP, result);
assertNotNull(starter.mMovedToTopActivity);
+ assertEquals(activityBot, outActivity[0]);
final ActivityStarter starter2 = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
| FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index c176658da847..e0b29c937381 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -382,13 +382,11 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
@Test
public void testResumeNextActivityOnCrashedAppDied() {
- mSupervisor.beginDeferResume();
final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
.setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
.build();
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setState(RESUMED, "test");
- mSupervisor.endDeferResume();
assertEquals(activity.app, mAtm.mInternal.getTopApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 27a4a2b69e31..70f57eb40385 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -357,6 +357,25 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
assertEquals(activity1.app, mAtm.mTopApp);
}
+ @Test
+ public void testTopResumedActivity_deferResume() {
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity2.setState(ActivityRecord.State.RESUMED, "test");
+ assertEquals(activity2.app, mAtm.mTopApp);
+ reset(activity2);
+
+ // Verify that no top-resumed activity changes to the client while defer-resume enabled.
+ mSupervisor.beginDeferResume();
+ activity1.getTask().moveToFront("test");
+ activity1.setState(ActivityRecord.State.RESUMED, "test");
+ verify(activity2, never()).scheduleTopResumedActivityChanged(eq(false));
+
+ // Verify that the change is scheduled to the client after defer-resumed disabled
+ mSupervisor.endDeferResume();
+ verify(activity2).scheduleTopResumedActivityChanged(eq(false));
+ }
+
/**
* We need to launch home again after user unlocked for those displays that do not have
* encryption aware home app.
@@ -376,7 +395,8 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
IBinder launchCookie = new Binder("test_launch_cookie");
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchCookie(launchCookie);
- SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle());
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid());
doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
anyInt(), any());
@@ -393,7 +413,8 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
- ActivityOptions.makeBasic().toBundle());
+ ActivityOptions.makeBasic().toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid());
doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 8227ed915c8e..00c9691835db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -143,6 +143,10 @@ class AppCompatActivityRobot {
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
}
+ void setDisplayIgnoreActivitySizeRestrictions(boolean enabled) {
+ doReturn(enabled).when(mDisplayContent).isDisplayIgnoreActivitySizeRestrictions();
+ }
+
void configureTaskBounds(@NonNull Rect taskBounds) {
doReturn(taskBounds).when(mTaskStack.top()).getBounds();
}
@@ -179,14 +183,23 @@ class AppCompatActivityRobot {
.getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
}
- void enableTreatmentForTopActivity(boolean enabled) {
- doReturn(enabled).when(mDisplayContent.mAppCompatCameraPolicy)
- .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+ void enableFullscreenCameraCompatTreatmentForTopActivity(boolean enabled) {
+ if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ doReturn(enabled).when(
+ mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy)
+ .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+ }
}
- void setTopActivityCameraActive(boolean enabled) {
+ void setIsCameraRunningAndWindowingModeEligibleFullscreen(boolean enabled) {
doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
- .isCameraActive(eq(mActivityStack.top()), /* mustBeFullscreen= */ eq(true));
+ .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()),
+ /* mustBeFullscreen= */ eq(true));
+ }
+
+ void setIsCameraRunningAndWindowingModeEligibleFreeform(boolean enabled) {
+ doReturn(enabled).when(getTopCameraCompatFreeformPolicy())
+ .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()));
}
void setTopActivityEligibleForOrientationOverride(boolean enabled) {
@@ -230,6 +243,14 @@ class AppCompatActivityRobot {
mDisplayContent.setIgnoreOrientationRequest(enabled);
}
+ void setTopActivityOrganizedTask() {
+ doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask();
+ }
+
+ void setIsInLetterboxAnimation(boolean inAnimation) {
+ doReturn(inAnimation).when(mActivityStack.top()).isInLetterboxAnimation();
+ }
+
void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode();
}
@@ -238,6 +259,10 @@ class AppCompatActivityRobot {
doReturn(embedded).when(mActivityStack.top()).isEmbedded();
}
+ void setTopActivityHasLetterboxedBounds(boolean letterboxed) {
+ doReturn(letterboxed).when(mActivityStack.top()).areBoundsLetterboxed();
+ }
+
void setTopActivityVisible(boolean isVisible) {
doReturn(isVisible).when(mActivityStack.top()).isVisible();
}
@@ -271,6 +296,10 @@ class AppCompatActivityRobot {
}
}
+ void setFixedRotationTransformDisplayBounds(@Nullable Rect bounds) {
+ doReturn(bounds).when(mActivityStack.top()).getFixedRotationTransformDisplayBounds();
+ }
+
void destroyTopActivity() {
mActivityStack.top().removeImmediately();
}
@@ -294,6 +323,8 @@ class AppCompatActivityRobot {
void createNewTaskWithBaseActivity() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setCreateActivity(true)
+ // Respect "@ChangeId" according to test package's target sdk.
+ .setPackage(mAtm.mContext.getPackageName())
.setDisplay(mDisplayContent).build();
mTaskStack.push(newTask);
pushActivity(newTask.getTopNonFinishingActivity());
@@ -503,8 +534,13 @@ class AppCompatActivityRobot {
}
private DisplayRotationCompatPolicy getTopDisplayRotationCompatPolicy() {
- return mActivityStack.top().mDisplayContent
- .mAppCompatCameraPolicy.mDisplayRotationCompatPolicy;
+ return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+ .mDisplayRotationCompatPolicy;
+ }
+
+ private CameraCompatFreeformPolicy getTopCameraCompatFreeformPolicy() {
+ return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy;
}
// We add the activity to the stack and spyOn() on its properties.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index 1e40aa0c8da8..14ef913e28db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -17,9 +17,12 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
@@ -29,10 +32,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
+import com.android.window.flags.Flags;
+
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -282,11 +288,99 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkFixedOrientationLetterboxAspectRatioForTopParent(/* expected */ 1.5f);
- robot.activity().enableTreatmentForTopActivity(/* enabled */ true);
+ robot.activity().enableFullscreenCameraCompatTreatmentForTopActivity(
+ /* enabled */ true);
robot.checkAspectRatioForTopParentIsSplitScreenRatio(/* expected */ true);
});
}
+ @Test
+ @EnableFlags(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API)
+ public void testHasFullscreenOverride_displayIgnoreActivitySizeRestrictionsTrue() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayIgnoreActivitySizeRestrictions(true);
+ a.createActivityWithComponent();
+ });
+
+ robot.checkHasFullscreenOverride(true);
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API)
+ public void testHasFullscreenOverride_displayIgnoreActivitySizeRestrictionsFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayIgnoreActivitySizeRestrictions(false);
+ a.createActivityWithComponent();
+ });
+
+ robot.checkHasFullscreenOverride(false);
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API)
+ public void testPropFalse_displayIgnoreActivitySizeRestrictionsTrue_notOverridden() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+ robot.applyOnActivity((a) -> {
+ a.setDisplayIgnoreActivitySizeRestrictions(true);
+ a.createActivityWithComponent();
+ });
+
+ robot.checkHasFullscreenOverride(false);
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API)
+ public void testPropTrue_displayIgnoreActivitySizeRestrictionsFalse_notOverridden() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+ robot.applyOnActivity((a) -> {
+ a.setDisplayIgnoreActivitySizeRestrictions(false);
+ a.createActivityWithComponent();
+ });
+
+ robot.checkHasFullscreenOverride(false);
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API)
+ public void testNotInSizeCompatMode_displayIgnoreActivitySizeRestrictionsTrue() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setDisplayIgnoreActivitySizeRestrictions(true);
+ a.configureTopActivity(/* minAspect */ -1f, /* maxAspect */-1f,
+ SCREEN_ORIENTATION_LANDSCAPE, true);
+ a.rotateDisplayForTopActivity(ROTATION_90);
+
+ a.checkTopActivityInSizeCompatMode(false);
+ });
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API)
+ public void testInSizeCompatMode_displayIgnoreActivitySizeRestrictionsFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setIgnoreOrientationRequest(true);
+ a.setDisplayIgnoreActivitySizeRestrictions(false);
+ a.configureTopActivity(/* minAspect */ -1f, /* maxAspect */-1f,
+ SCREEN_ORIENTATION_LANDSCAPE, true);
+ a.rotateDisplayForTopActivity(ROTATION_90);
+
+ a.checkTopActivityInSizeCompatMode(true);
+ });
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -308,6 +402,12 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
super.onPostDisplayContentCreation(displayContent);
spyOn(displayContent.mAppCompatCameraPolicy);
+ if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+ }
+ if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
}
@Override
@@ -359,6 +459,11 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
}
}
+ void checkHasFullscreenOverride(boolean expected) {
+ assertEquals(expected,
+ getTopActivityAppCompatAspectRatioOverrides().hasFullscreenOverride());
+ }
+
private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() {
return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index d66c21a77fcd..1d138e4a48d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
@@ -28,7 +28,7 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_V
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import android.compat.testing.PlatformCompatChangeRule;
import android.platform.test.annotations.DisableFlags;
@@ -218,7 +218,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -228,9 +228,8 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
- public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldApplyCameraCompatFreeformTreatment_notEnabledByOverride_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -239,30 +238,33 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
- public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() {
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldApplyCameraCompatFreeformTreatment_overrideAndFlagEnabled_returnsTrue() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
- robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
});
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
- public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldApplyCameraCompatFreeformTreatment_enabledByShellCommand_returnsTrue() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
+ robot.setCameraCompatTreatmentEnabledViaShellCommand(true);
+
robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
});
}
@Test
@EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
- OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void testShouldRecomputeConfigurationForCameraCompat() {
+ OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA,
+ OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testShouldRecomputeConfigurationForFreeformTreatment() {
runTestScenario((robot) -> {
robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
robot.applyOnActivity((a) -> {
@@ -360,6 +362,11 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
spyOn(displayContent.mAppCompatCameraPolicy);
}
+ void setCameraCompatTreatmentEnabledViaShellCommand(boolean enabled) {
+ activity().top().mWmService.mAppCompatConfiguration
+ .setIsCameraCompatFreeformWindowingTreatmentEnabled(enabled);
+ }
+
void checkShouldRefreshActivityForCameraCompat(boolean expected) {
Assert.assertEquals(getAppCompatCameraOverrides()
.shouldRefreshActivityForCameraCompat(), expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index d91b38efd40b..cb5afd830619 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -18,13 +18,13 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.server.wm.AppCompatCameraPolicy.isTreatmentEnabledForActivity;
+import static com.android.server.wm.AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import android.compat.testing.PlatformCompatChangeRule;
import android.platform.test.annotations.DisableFlags;
@@ -85,50 +85,50 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
});
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ false);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ false);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
});
}
@Test
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
});
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ false);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ false);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
});
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_startedWhenEnabledAndDW() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
robot.checkTopActivityCameraCompatFreeformPolicyIsRunning();
@@ -136,10 +136,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_existsWhenCameraCompatFreeformExists() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(true);
+ robot.dw().allowEnterDesktopMode(true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
@@ -147,10 +147,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_startedWhenCameraCompatFreeformExists() {
runTestScenario((robot) -> {
- robot.allowEnterDesktopMode(true);
+ robot.dw().allowEnterDesktopMode(true);
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
@@ -180,7 +180,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_doesNotExistWhenNoPolicyExists() {
runTestScenario((robot) -> {
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false);
@@ -194,9 +194,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
@Test
public void testIsCameraCompatTreatmentActive_whenTreatmentForTopActivityIsEnabled() {
runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
robot.applyOnActivity((a)-> {
- a.createActivityWithComponent();
- a.enableTreatmentForTopActivity(/* enabled */ true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ true);
});
robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ true);
@@ -206,9 +207,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
@Test
public void testIsCameraCompatTreatmentNotActive_whenTreatmentForTopActivityIsDisabled() {
runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
robot.applyOnActivity((a)-> {
a.createActivityWithComponent();
- a.enableTreatmentForTopActivity(/* enabled */ false);
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ false);
});
robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ false);
@@ -220,9 +222,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsNotRunning() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
+ robot.dw().allowEnterDesktopMode(true);
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ false);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* enabled */ false);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -234,9 +237,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideDisabled() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
+ robot.dw().allowEnterDesktopMode(true);
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ true);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -245,12 +249,28 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
@Test
@EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
- public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideEnabled() {
+ public void testShouldOverrideMinAspectRatioForCameraFullscr_cameraIsRunning_overrideEnabled() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ true);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
+ });
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
+ });
+ }
+
+
+ @Test
+ @EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldOverrideMinAspectRatioForCameraFreeform_cameraRunning_overrideEnabled() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a)-> {
+ robot.dw().allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIsCameraRunningAndWindowingModeEligibleFreeform(/* active */ true);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
@@ -318,23 +338,11 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
- assertEquals(getTopAppCompatCameraPolicy()
- .isTreatmentEnabledForActivity(activity().top()), active);
+ assertEquals(active, isTreatmentEnabledForActivity(activity().top()));
}
void checkShouldOverrideMinAspectRatioForCamera(boolean expected) {
- assertEquals(getTopAppCompatCameraPolicy()
- .shouldOverrideMinAspectRatioForCamera(activity().top()), expected);
- }
-
- // TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
- void allowEnterDesktopMode(boolean isAllowed) {
- doReturn(isAllowed).when(() ->
- DesktopModeHelper.canEnterDesktopMode(any()));
- }
-
- private AppCompatCameraPolicy getTopAppCompatCameraPolicy() {
- return activity().top().mDisplayContent.mAppCompatCameraPolicy;
+ assertEquals(expected, shouldOverrideMinAspectRatioForCamera(activity().top()));
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
new file mode 100644
index 000000000000..673d04166a7a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxInnerBounds;
+import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxOuterBounds;
+import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxPosition;
+
+import static org.mockito.Mockito.mock;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for the {@link AppCompatLetterboxUtils} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:AppCompatLetterboxUtilsTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatLetterboxUtilsTest extends WindowTestsBase {
+
+ @Test
+ public void allEmptyWhenIsAppNotLetterboxed() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(false);
+ robot.getLetterboxPosition();
+ robot.assertPosition(/* x */ 0, /* y */0);
+ robot.getInnerBounds();
+ robot.assertInnerBounds(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0);
+ robot.getOuterBounds();
+ robot.assertOuterBounds(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0);
+ });
+ }
+
+ @Test
+ public void positionIsFromTaskWhenLetterboxAnimationIsRunning() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.activity().setIsInLetterboxAnimation(true);
+ robot.activity().configureTaskBounds(
+ new Rect(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ 400));
+ robot.getLetterboxPosition();
+
+ robot.assertPosition(/* x */ 100, /* y */ 200);
+ });
+ }
+
+ @Test
+ public void positionIsFromActivityWhenLetterboxAnimationIsNotRunning() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.activity().setIsInLetterboxAnimation(false);
+ robot.activity().configureTopActivityBounds(
+ new Rect(/* left */ 200, /* top */ 400, /* right */ 300, /* bottom */ 400));
+ robot.getLetterboxPosition();
+
+ robot.assertPosition(/* x */ 200, /* y */ 400);
+ });
+ }
+
+ @Test
+ public void outerBoundsWhenFixedRotationTransformDisplayBoundsIsAvailable() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.activity().setFixedRotationTransformDisplayBounds(
+ new Rect(/* left */ 1, /* top */ 2, /* right */ 3, /* bottom */ 4));
+ robot.getOuterBounds();
+
+ robot.assertOuterBounds(/* left */ 1, /* top */ 2, /* right */ 3, /* bottom */ 4);
+ });
+ }
+
+ @Test
+ public void outerBoundsNoFixedRotationTransformDisplayBoundsInMultiWindow() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.activity().setFixedRotationTransformDisplayBounds(null);
+ robot.activity().setTopActivityInMultiWindowMode(true);
+ robot.getOuterBounds();
+
+ robot.checkOuterBoundsAreTaskFragmentBounds();
+ });
+ }
+
+ @Test
+ public void outerBoundsNoFixedRotationTransformDisplayBoundsNotInMultiWindow() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.activity().setFixedRotationTransformDisplayBounds(null);
+ robot.activity().setTopActivityInMultiWindowMode(false);
+ robot.getOuterBounds();
+
+ robot.checkOuterBoundsAreRootTaskParentBounds();
+ });
+ }
+
+ @Test
+ public void innerBoundsTransparencyPolicyIsRunning() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.setTopActivityTransparentPolicyRunning(true);
+
+ robot.getInnerBounds();
+
+ robot.checkInnerBoundsAreActivityBounds();
+ });
+ }
+
+ @Test
+ public void innerBoundsTransparencyPolicyIsNotRunning() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityLetterboxPolicyRunning(true);
+ robot.setTopActivityTransparentPolicyRunning(false);
+ robot.setWindowFrame(
+ new Rect(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ 400));
+
+ robot.getInnerBounds();
+
+ robot.assertInnerBounds(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */
+ 400);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<LetterboxUtilsRobotTest> consumer) {
+ final LetterboxUtilsRobotTest robot = new LetterboxUtilsRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class LetterboxUtilsRobotTest extends AppCompatRobotBase {
+
+ private final Point mPosition = new Point();
+ private final Rect mInnerBound = new Rect();
+ private final Rect mOuterBound = new Rect();
+
+ @NonNull
+ private final WindowState mWindowState;
+
+ LetterboxUtilsRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ mWindowState = mock(WindowState.class);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatLetterboxPolicy());
+ spyOn(activity.mAppCompatController.getTransparentPolicy());
+ }
+
+ void setTopActivityLetterboxPolicyRunning(boolean running) {
+ doReturn(running).when(activity().top().mAppCompatController
+ .getAppCompatLetterboxPolicy()).isRunning();
+ }
+
+ void setTopActivityTransparentPolicyRunning(boolean running) {
+ doReturn(running).when(activity().top().mAppCompatController
+ .getTransparentPolicy()).isRunning();
+ }
+
+ void setWindowFrame(@NonNull Rect frame) {
+ doReturn(frame).when(mWindowState).getFrame();
+ }
+
+ void getLetterboxPosition() {
+ calculateLetterboxPosition(activity().top(), mPosition);
+ }
+
+ void getInnerBounds() {
+ calculateLetterboxInnerBounds(activity().top(), mWindowState, mInnerBound);
+ }
+
+ void getOuterBounds() {
+ calculateLetterboxOuterBounds(activity().top(), mOuterBound);
+ }
+
+ void assertPosition(int expectedX, int expectedY) {
+ Assert.assertEquals(expectedX, mPosition.x);
+ Assert.assertEquals(expectedY, mPosition.y);
+ }
+
+ void assertInnerBounds(int expectedLeft, int expectedTop, int expectedRight,
+ int expectedBottom) {
+ Assert.assertEquals(expectedLeft, mInnerBound.left);
+ Assert.assertEquals(expectedTop, mInnerBound.top);
+ Assert.assertEquals(expectedRight, mInnerBound.right);
+ Assert.assertEquals(expectedBottom, mInnerBound.bottom);
+ }
+
+ void assertOuterBounds(int expectedLeft, int expectedTop, int expectedRight,
+ int expectedBottom) {
+ Assert.assertEquals(expectedLeft, mOuterBound.left);
+ Assert.assertEquals(expectedTop, mOuterBound.top);
+ Assert.assertEquals(expectedRight, mOuterBound.right);
+ Assert.assertEquals(expectedBottom, mOuterBound.bottom);
+ }
+
+ void checkOuterBoundsAreRootTaskParentBounds() {
+ Assert.assertEquals(mOuterBound,
+ activity().top().getRootTask().getParent().getBounds());
+ }
+
+ void checkOuterBoundsAreTaskFragmentBounds() {
+ Assert.assertEquals(mOuterBound,
+ activity().top().getTaskFragment().getBounds());
+ }
+
+ void checkInnerBoundsAreActivityBounds() {
+ Assert.assertEquals(mInnerBound, activity().top().getBounds());
+ }
+
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index d9b5f37be86c..8747cfae93f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -17,11 +17,13 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP;
import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS;
@@ -31,6 +33,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.compat.testing.PlatformCompatChangeRule;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -228,6 +231,25 @@ public class AppCompatOrientationOverridesTest extends WindowTestsBase {
});
}
+ @Test
+ public void testOverrideRespectRequestedOrientationIsEnabled_bottomOrientationIsRespected() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponentInNewTask();
+ robot.setOverrideRespectRequestedOrientationEnabled(true);
+ a.configureUnresizableTopActivity(SCREEN_ORIENTATION_LANDSCAPE);
+ robot.checkDisplayShouldIgnoreOrientationRequest(SCREEN_ORIENTATION_LANDSCAPE,
+ /* expected */ false);
+
+ a.createActivityWithComponentInNewTask();
+ a.setTopActivityInFreeformWindowingMode(true);
+ });
+ robot.checkDisplayShouldIgnoreOrientationRequest(SCREEN_ORIENTATION_LANDSCAPE,
+ /* expected */ false);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -291,6 +313,22 @@ public class AppCompatOrientationOverridesTest extends WindowTestsBase {
}
}
+ void setOverrideRespectRequestedOrientationEnabled(boolean override) {
+ spyOn(getTopOrientationOverrides());
+ doReturn(override).when(getTopOrientationOverrides())
+ .isOverrideRespectRequestedOrientationEnabled();
+ }
+
+ void checkDisplayShouldIgnoreOrientationRequest(@ScreenOrientation int candidate,
+ boolean expected) {
+ assertEquals(expected, activity().displayContent()
+ .shouldIgnoreOrientationRequest(candidate));
+ }
+
+ void checkExpectedDisplayOrientation(@ScreenOrientation int expected) {
+ assertEquals(expected, activity().displayContent().getOrientation());
+ }
+
void checkShouldUseDisplayLandscapeNaturalOrientation(boolean expected) {
assertEquals(expected,
getTopOrientationOverrides().shouldUseDisplayLandscapeNaturalOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 9057b6cb99ea..09ed9baba096 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -38,6 +38,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
@@ -45,6 +46,7 @@ import static org.mockito.Mockito.verify;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -321,7 +323,22 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
});
robot.applyOnActivity((a) -> {
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(false);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(false);
+ });
+
+ robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
+ /* expected */ SCREEN_ORIENTATION_PORTRAIT);
+ });
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testOverrideOrientationIfNeeded_fullscrOverrideFreeform_cameraActivity_unchanged() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ robot.dw().allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIsCameraRunningAndWindowingModeEligibleFreeform(false);
});
robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -426,8 +443,8 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
c.enablePolicyForIgnoringRequestedOrientation(true);
});
robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
- a.enableTreatmentForTopActivity(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(true);
});
robot.prepareRelaunchingAfterRequestedOrientationChanged(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 5f2a63aa9eab..0d929abeb34a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -39,6 +39,8 @@ abstract class AppCompatRobotBase {
private final AppCompatComponentPropRobot mOptPropRobot;
@NonNull
private final AppCompatResourcesRobot mResourcesRobot;
+ @NonNull
+ private final DesktopWindowingRobot mDesktopWindowingRobot;
AppCompatRobotBase(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm,
@@ -51,6 +53,7 @@ abstract class AppCompatRobotBase {
new AppCompatConfigurationRobot(wm.mAppCompatConfiguration);
mOptPropRobot = new AppCompatComponentPropRobot(wm);
mResourcesRobot = new AppCompatResourcesRobot(wm.mContext.getResources());
+ mDesktopWindowingRobot = new DesktopWindowingRobot();
}
AppCompatRobotBase(@NonNull WindowManagerService wm,
@@ -111,6 +114,11 @@ abstract class AppCompatRobotBase {
return mResourcesRobot;
}
+ @NonNull
+ DesktopWindowingRobot dw() {
+ return mDesktopWindowingRobot;
+ }
+
void applyOnResources(@NonNull Consumer<AppCompatResourcesRobot> consumer) {
consumer.accept(mResourcesRobot);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 21fac9bcd1e4..a5b2cb39cfff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -16,14 +16,29 @@
package com.android.server.wm;
+import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
+import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
+import android.app.TaskInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.Surface;
import androidx.annotation.NonNull;
+import com.android.window.flags.Flags;
+
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -114,6 +129,104 @@ public class AppCompatUtilsTest extends WindowTestsBase {
});
}
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_eligible() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.setIgnoreOrientationRequest(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(true);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(true);
+ });
+ }
+
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_disabled_notEligible() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.setIgnoreOrientationRequest(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(false);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(false);
+ });
+ }
+
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_inSizeCompatMode_notEligible() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.setIgnoreOrientationRequest(true);
+ a.setTopActivityOrganizedTask();
+ a.setTopActivityInSizeCompatMode(true);
+ a.setTopActivityVisible(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(true);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(false);
+ });
+ }
+
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_transparentTop_notEligible() {
+ runTestScenario((robot) -> {
+ robot.transparentActivity((ta) -> {
+ ta.launchTransparentActivityInTask();
+ ta.activity().setIgnoreOrientationRequest(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(true);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(false);
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void getTaskInfoPropagatesCameraCompatMode() {
+ runTestScenario((robot) -> {
+ robot.dw().allowEnterDesktopMode(/* isAllowed= */ true);
+ robot.applyOnActivity(
+ AppCompatActivityRobot::createActivityWithComponentInNewTaskAndDisplay);
+ robot.setCameraCompatTreatmentEnabledForActivity(/* enabled= */ true);
+
+ robot.setFreeformCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+ robot.checkTaskInfoFreeformCameraCompatMode(
+ CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+ });
+ }
+
+ @Test
+ public void testTopActivityLetterboxed_hasBounds() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ a.setIgnoreOrientationRequest(true);
+ a.configureTopActivityBounds(new Rect(20, 30, 520, 630));
+ });
+ robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ true);
+
+
+ robot.checkTaskInfoTopActivityHasBounds(/* expected */ new Rect(20, 30, 520, 630));
+ });
+ }
+
+ @Test
+ public void testTopActivityNotLetterboxed_hasNoBounds() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setIgnoreOrientationRequest(true);
+ });
+
+ robot.checkTaskInfoTopActivityHasBounds(/* expected */ null);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -125,11 +238,14 @@ public class AppCompatUtilsTest extends WindowTestsBase {
private static class AppCompatUtilsRobotTest extends AppCompatRobotBase {
private final WindowState mWindowState;
+ @NonNull
+ private final AppCompatTransparentActivityRobot mTransparentActivityRobot;
AppCompatUtilsRobotTest(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm,
@NonNull ActivityTaskSupervisor supervisor) {
super(wm, atm, supervisor);
+ mTransparentActivityRobot = new AppCompatTransparentActivityRobot(activity());
mWindowState = Mockito.mock(WindowState.class);
}
@@ -139,6 +255,21 @@ public class AppCompatUtilsTest extends WindowTestsBase {
spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
}
+ @Override
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ super.onPostDisplayContentCreation(displayContent);
+ mockPortraitDisplay(displayContent);
+ if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
+ }
+
+ void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) {
+ // We always create at least an opaque activity in a Task.
+ activity().createNewTaskWithBaseActivity();
+ consumer.accept(mTransparentActivityRobot);
+ }
+
void setIsLetterboxedForFixedOrientationAndAspectRatio(
boolean forFixedOrientationAndAspectRatio) {
when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
@@ -155,11 +286,53 @@ public class AppCompatUtilsTest extends WindowTestsBase {
when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout);
}
+ void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) {
+ doReturn(mode).when(activity().top().mDisplayContent.mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy).getCameraCompatMode(activity().top());
+ }
+
void checkTopActivityLetterboxReason(@NonNull String expected) {
Assert.assertEquals(expected,
AppCompatUtils.getLetterboxReasonString(activity().top(), mWindowState));
}
- }
+ @NonNull
+ TaskInfo getTopTaskInfo() {
+ return activity().top().getTask().getTaskInfo();
+ }
+ void checkTaskInfoEligibleForUserAspectRatioButton(boolean eligible) {
+ Assert.assertEquals(eligible, getTopTaskInfo().appCompatTaskInfo
+ .eligibleForUserAspectRatioButton());
+ }
+
+ void checkTaskInfoFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) {
+ Assert.assertEquals(mode, getTopTaskInfo().appCompatTaskInfo
+ .cameraCompatTaskInfo.freeformCameraCompatMode);
+ }
+
+ void checkTaskInfoTopActivityHasBounds(Rect bounds) {
+ Assert.assertEquals(bounds, getTopTaskInfo().appCompatTaskInfo
+ .topActivityLetterboxBounds);
+ }
+
+ void setCameraCompatTreatmentEnabledForActivity(boolean enabled) {
+ doReturn(enabled).when(activity().displayContent().mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy).isTreatmentEnabledForActivity(
+ eq(activity().top()), anyBoolean());
+ }
+
+ private void mockPortraitDisplay(DisplayContent displayContent) {
+ doAnswer(invocation -> {
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayContent.getDisplay().getDisplayInfo(displayInfo);
+ displayInfo.rotation = Surface.ROTATION_90;
+ // Set height and width so that the natural orientation (when rotation is 0) is
+ // portrait.
+ displayInfo.logicalHeight = 600;
+ displayInfo.logicalWidth = 800;
+ return displayInfo;
+ }).when(displayContent.mWmService.mDisplayManagerInternal).getDisplayInfo(anyInt());
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 20dcdde63cc4..40da9ea2d718 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -24,7 +24,9 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.window.BackNavigationInfo.typeToString;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -64,6 +66,7 @@ import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
+import android.window.IBackAnimationHandoffHandler;
import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedCallbackInfo;
@@ -103,6 +106,13 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Before
public void setUp() throws Exception {
+ final TransitionController transitionController = mAtm.getTransitionController();
+ final Transition fakeTransition = new Transition(TRANSIT_PREPARE_BACK_NAVIGATION,
+ 0 /* flag */, transitionController, transitionController.mSyncEngine);
+ spyOn(transitionController);
+ doReturn(fakeTransition).when(transitionController)
+ .createTransition(anyInt(), anyInt());
+
final BackNavigationController original = new BackNavigationController();
original.setWindowManager(mWm);
mBackNavigationController = Mockito.spy(original);
@@ -111,6 +121,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
mBackAnimationAdapter = mock(BackAnimationAdapter.class);
doReturn(true).when(mBackAnimationAdapter).isAnimatable(anyInt());
+ Mockito.doNothing().when(mBackNavigationController).startAnimation();
mNavigationMonitor = mock(BackNavigationController.NavigationMonitor.class);
mRootHomeTask = initHomeActivity();
}
@@ -446,7 +457,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -467,7 +478,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ true));
+ /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -608,7 +619,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(backNavigationInfo).isNull();
@@ -722,7 +733,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_SYSTEM,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
return callback;
}
@@ -732,7 +743,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
return callback;
}
@@ -770,6 +781,10 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Override
public void setTriggerBack(boolean triggerBack) {
}
+
+ @Override
+ public void setHandoffHandler(IBackAnimationHandoffHandler unused) {
+ }
};
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index 3910904337b2..da010ae3c96a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -23,11 +23,13 @@ import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_I
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_NON_APP_VISIBLE_WINDOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
@@ -39,7 +41,6 @@ import static org.mockito.Mockito.when;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
-import android.app.BackgroundStartPrivileges;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -51,6 +52,7 @@ import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.DeviceConfig;
import android.util.Pair;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -127,6 +129,8 @@ public class BackgroundActivityStartControllerExemptionTests {
AppOpsManager mAppOpsManager;
MirrorActiveUids mActiveUids = new MirrorActiveUids();
WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
+ @Mock
+ VisibleActivityProcessTracker mVisibleActivityProcessTracker;
@Mock
ActivityTaskSupervisor mSupervisor;
@@ -183,6 +187,8 @@ public class BackgroundActivityStartControllerExemptionTests {
mService.mRootWindowContainer = mRootWindowContainer;
when(mService.getAppOpsManager()).thenReturn(mAppOpsManager);
setViaReflection(mService, "mProcessMap", mProcessMap);
+ setViaReflection(mService, "mVisibleActivityProcessTracker",
+ mVisibleActivityProcessTracker);
setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
@@ -223,12 +229,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
assertWithMessage(balState.toString()).that(balState.isPendingIntent()).isTrue();
@@ -258,18 +264,20 @@ public class BackgroundActivityStartControllerExemptionTests {
int realCallingPid = REGULAR_PID_2;
// setup state
- when(mService.hasActiveVisibleWindow(eq(callingUid))).thenReturn(true);
+ when(mVisibleActivityProcessTracker.hasVisibleActivity(eq(callingUid))).thenReturn(true);
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
+ assertThat(balState.toString()).contains("callingUidHasVisibleActivity: true");
+ assertThat(balState.toString()).contains("callingUidHasNonAppVisibleWindow: false");
// call
BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
@@ -290,18 +298,21 @@ public class BackgroundActivityStartControllerExemptionTests {
int realCallingPid = REGULAR_PID_2;
// setup state
- when(mService.hasActiveVisibleWindow(eq(realCallingUid))).thenReturn(true);
+ when(mVisibleActivityProcessTracker.hasVisibleActivity(eq(realCallingUid))).thenReturn(
+ true);
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
+ assertThat(balState.toString()).contains("realCallingUidHasVisibleActivity: true");
+ assertThat(balState.toString()).contains("realCallingUidHasNonAppVisibleWindow: false");
// call
BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
@@ -314,6 +325,76 @@ public class BackgroundActivityStartControllerExemptionTests {
}
@Test
+ public void testCaller_appHasNonAppVisibleWindow() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ mActiveUids.onNonAppSurfaceVisibilityChanged(callingUid,
+ WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY, true);
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ boolean allowBalExemptionForSystemProcess = false;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
+ checkedOptions);
+ assertThat(balState.toString()).contains("callingUidHasVisibleActivity: false");
+ assertThat(balState.toString()).contains("callingUidHasNonAppVisibleWindow: true");
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+ balState);
+ balState.setResultForCaller(callerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_NON_APP_VISIBLE_WINDOW);
+ }
+
+ @Test
+ public void testRealCaller_appHasNonAppVisibleWindow() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ mActiveUids.onNonAppSurfaceVisibilityChanged(realCallingUid,
+ WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY, true);
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ boolean allowBalExemptionForSystemProcess = false;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
+ checkedOptions);
+ assertThat(balState.toString()).contains("realCallingUidHasVisibleActivity: false");
+ assertThat(balState.toString()).contains("realCallingUidHasNonAppVisibleWindow: true");
+
+ // call
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
+ balState);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_NON_APP_VISIBLE_WINDOW);
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public void testCaller_appHasVisibleWindowWithIfVisibleOptIn() {
int callingUid = REGULAR_UID_1;
@@ -323,20 +404,22 @@ public class BackgroundActivityStartControllerExemptionTests {
int realCallingPid = REGULAR_PID_2;
// setup state
- when(mService.hasActiveVisibleWindow(eq(callingUid))).thenReturn(true);
+ when(mVisibleActivityProcessTracker.hasVisibleActivity(eq(callingUid))).thenReturn(true);
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions
.setPendingIntentCreatorBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
+ assertThat(balState.toString()).contains("callingUidHasVisibleActivity: true");
+ assertThat(balState.toString()).contains("callingUidHasNonAppVisibleWindow: false");
// call
BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
@@ -358,20 +441,23 @@ public class BackgroundActivityStartControllerExemptionTests {
int realCallingPid = REGULAR_PID_2;
// setup state
- when(mService.hasActiveVisibleWindow(eq(realCallingUid))).thenReturn(true);
+ when(mVisibleActivityProcessTracker.hasVisibleActivity(eq(realCallingUid))).thenReturn(
+ true);
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions
.setPendingIntentCreatorBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
+ assertThat(balState.toString()).contains("realCallingUidHasVisibleActivity: true");
+ assertThat(balState.toString()).contains("realCallingUidHasNonAppVisibleWindow: false");
// call
BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
@@ -405,12 +491,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -441,12 +527,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -490,12 +576,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -526,12 +612,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -563,14 +649,14 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions
.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -597,12 +683,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -630,14 +716,14 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
checkedOptions.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -664,12 +750,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -683,6 +769,41 @@ public class BackgroundActivityStartControllerExemptionTests {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
+ public void testRealCaller_sawPermission() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ when(mService.hasSystemAlertWindowPermission(eq(realCallingUid), eq(realCallingPid),
+ any())).thenReturn(true);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ boolean allowBalExemptionForSystemProcess = false;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions =
+ mCheckedOptions.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
+ balState);
+ balState.setResultForCaller(callerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_SAW_PERMISSION);
+ }
+
+ @Test
public void testCaller_isRecents() {
int callingUid = REGULAR_UID_1;
int callingPid = REGULAR_PID_1;
@@ -698,12 +819,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -729,12 +850,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -760,12 +881,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
@@ -796,12 +917,12 @@ public class BackgroundActivityStartControllerExemptionTests {
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// call
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
index 23b1c4b9e150..99e730ae76cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
@@ -23,7 +23,6 @@ import static com.android.server.wm.BackgroundActivityStartControllerTests.setVi
import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityOptions;
-import android.app.BackgroundStartPrivileges;
import android.content.Intent;
import android.platform.test.annotations.Presubmit;
@@ -68,12 +67,16 @@ public class BackgroundActivityStartControllerLogTests {
@Mock
PendingIntentRecord mPendingIntentRecord;
MirrorActiveUids mActiveUids = new MirrorActiveUids();
+ @Mock
+ VisibleActivityProcessTracker mVisibleActivityProcessTracker;
BackgroundActivityStartController mController;
BackgroundActivityStartController.BalState mState;
@Before
public void setup() {
setViaReflection(mService, "mActiveUids", mActiveUids);
+ setViaReflection(mService, "mVisibleActivityProcessTracker",
+ mVisibleActivityProcessTracker);
mController = new BackgroundActivityStartController(mService,
mSupervisor);
}
@@ -189,7 +192,7 @@ public class BackgroundActivityStartControllerLogTests {
private void useIntent(int uid) {
mState = mController.new BalState(uid, APP1_PID,
"calling.package", uid, APP1_PID, null,
- null, BackgroundStartPrivileges.NONE, null, new Intent(),
+ null, false, null, new Intent(),
ActivityOptions.makeBasic());
}
@@ -200,7 +203,7 @@ public class BackgroundActivityStartControllerLogTests {
private void usePendingIntent(int callerUid, int realCallerUid) {
mState = mController.new BalState(callerUid, APP1_PID,
"calling.package", realCallerUid, APP2_PID, null,
- mPendingIntentRecord, BackgroundStartPrivileges.NONE, null, new Intent(),
+ mPendingIntentRecord, false, null, new Intent(),
ActivityOptions.makeBasic());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 6ec789599482..854bda03f18d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -111,6 +111,9 @@ public class BackgroundActivityStartControllerTests {
@Mock
AppOpsManager mAppOpsManager;
MirrorActiveUids mActiveUids = new MirrorActiveUids();
+ @Mock
+ VisibleActivityProcessTracker mVisibleActivityProcessTracker;
+
WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
@Mock
@@ -194,11 +197,14 @@ public class BackgroundActivityStartControllerTests {
mService.mTaskSupervisor = mSupervisor;
mService.mContext = mContext;
setViaReflection(mService, "mActiveUids", mActiveUids);
+ setViaReflection(mService, "mGlobalLock", new WindowManagerGlobalLock());
Mockito.when(mService.getPackageManagerInternalLocked()).thenReturn(
mPackageManagerInternal);
mService.mRootWindowContainer = mRootWindowContainer;
Mockito.when(mService.getAppOpsManager()).thenReturn(mAppOpsManager);
setViaReflection(mService, "mProcessMap", mProcessMap);
+ setViaReflection(mService, "mVisibleActivityProcessTracker",
+ mVisibleActivityProcessTracker);
//Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController);
setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
@@ -241,14 +247,14 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -276,14 +282,14 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -311,14 +317,14 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -346,14 +352,14 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -380,14 +386,14 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -418,7 +424,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
@@ -429,7 +435,7 @@ public class BackgroundActivityStartControllerTests {
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -457,7 +463,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic()
.setPendingIntentCreatorBackgroundActivityStartMode(
@@ -466,7 +472,7 @@ public class BackgroundActivityStartControllerTests {
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -494,7 +500,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
@@ -503,7 +509,7 @@ public class BackgroundActivityStartControllerTests {
// call
BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
callingPackage, realCallingUid, realCallingPid, mCallerApp,
- originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ originatingPendingIntent, allowBalExemptionForSystemProcess, mResultRecord, intent,
checkedOptions);
// assertions
@@ -530,7 +536,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
WindowProcessController callerApp = mCallerApp;
@@ -539,8 +545,8 @@ public class BackgroundActivityStartControllerTests {
// call
BackgroundActivityStartController.BalState balState = mController
.new BalState(callingUid, callingPid, callingPackage, realCallingUid,
- realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
- resultRecord, intent, checkedOptions);
+ realCallingPid, callerApp, originatingPendingIntent,
+ allowBalExemptionForSystemProcess, resultRecord, intent, checkedOptions);
// assertions
assertThat(balState.mAutoOptInReason).isEqualTo("notPendingIntent");
@@ -550,16 +556,17 @@ public class BackgroundActivityStartControllerTests {
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).startsWith(
+ assertThat(balState.toString()).isEqualTo(
"[callingPackage: package.app1; "
+ "callingPackageTargetSdk: -1; "
+ "callingUid: 10001; "
+ "callingPid: 11001; "
+ "appSwitchState: 0; "
- + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidHasVisibleActivity: false; "
+ + "callingUidHasNonAppVisibleWindow: false; "
+ "callingUidProcState: NONEXISTENT; "
+ "isCallingUidPersistentSystemProcess: false; "
- + "forcedBalByPiSender: BSP.NONE; "
+ + "allowBalExemptionForSystemProcess: false; "
+ "intent: Intent { cmp=package.app3/someClass }; "
+ "callerApp: mCallerApp; "
+ "inVisibleTask: false; "
@@ -575,13 +582,17 @@ public class BackgroundActivityStartControllerTests {
+ "realCallingPackageTargetSdk: -1; "
+ "realCallingUid: 1; "
+ "realCallingPid: 1; "
- + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidHasVisibleActivity: false; "
+ + "realCallingUidHasNonAppVisibleWindow: false; "
+ "realCallingUidProcState: NONEXISTENT; "
+ "isRealCallingUidPersistentSystemProcess: false; "
+ "originatingPendingIntent: null; "
+ "realCallerApp: null; "
+ "balAllowedByPiSender: BSP.ALLOW_BAL; "
- + "resultIfPiSenderAllowsBal: null");
+ + "resultIfPiSenderAllowsBal: null; "
+ + "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ + "balRequireOptInByPendingIntentCreator: true; "
+ + "balDontBringExistingBackgroundTaskStackToFg: true]");
}
@Test
@@ -596,7 +607,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
WindowProcessController callerApp = mCallerApp;
@@ -605,8 +616,8 @@ public class BackgroundActivityStartControllerTests {
// call
BackgroundActivityStartController.BalState balState = mController
.new BalState(callingUid, callingPid, callingPackage, realCallingUid,
- realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
- resultRecord, intent, checkedOptions);
+ realCallingPid, callerApp, originatingPendingIntent,
+ allowBalExemptionForSystemProcess, resultRecord, intent, checkedOptions);
// assertions
assertThat(balState.mAutoOptInReason).isEqualTo("callForResult");
@@ -629,7 +640,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingUid = NO_UID;
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
- BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ boolean allowBalExemptionForSystemProcess = false;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
WindowProcessController callerApp = mCallerApp;
@@ -638,8 +649,8 @@ public class BackgroundActivityStartControllerTests {
// call
BackgroundActivityStartController.BalState balState = mController
.new BalState(callingUid, callingPid, callingPackage, realCallingUid,
- realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
- resultRecord, intent, checkedOptions);
+ realCallingPid, callerApp, originatingPendingIntent,
+ allowBalExemptionForSystemProcess, resultRecord, intent, checkedOptions);
// assertions
assertThat(balState.mAutoOptInReason).isNull();
@@ -650,16 +661,17 @@ public class BackgroundActivityStartControllerTests {
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).startsWith(
+ assertThat(balState.toString()).isEqualTo(
"[callingPackage: package.app1; "
+ "callingPackageTargetSdk: -1; "
+ "callingUid: 10001; "
+ "callingPid: 11001; "
+ "appSwitchState: 0; "
- + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidHasVisibleActivity: false; "
+ + "callingUidHasNonAppVisibleWindow: false; "
+ "callingUidProcState: NONEXISTENT; "
+ "isCallingUidPersistentSystemProcess: false; "
- + "forcedBalByPiSender: BSP.NONE; "
+ + "allowBalExemptionForSystemProcess: false; "
+ "intent: Intent { cmp=package.app3/someClass }; "
+ "callerApp: mCallerApp; "
+ "inVisibleTask: false; "
@@ -675,12 +687,16 @@ public class BackgroundActivityStartControllerTests {
+ "realCallingPackageTargetSdk: -1; "
+ "realCallingUid: 1; "
+ "realCallingPid: 1; "
- + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidHasVisibleActivity: false; "
+ + "realCallingUidHasNonAppVisibleWindow: false; "
+ "realCallingUidProcState: NONEXISTENT; "
+ "isRealCallingUidPersistentSystemProcess: false; "
+ "originatingPendingIntent: PendingIntentRecord; "
+ "realCallerApp: null; "
+ "balAllowedByPiSender: BSP.ALLOW_FGS; "
- + "resultIfPiSenderAllowsBal: null");
+ + "resultIfPiSenderAllowsBal: null; "
+ + "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ + "balRequireOptInByPendingIntentCreator: true; "
+ + "balDontBringExistingBackgroundTaskStackToFg: true]");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index a48813d775d1..3750dd38aa8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -25,17 +25,22 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -44,22 +49,27 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.CameraCompatTaskInfo;
+import android.app.IApplicationThread;
import android.app.WindowConfiguration.WindowingMode;
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Configuration.Orientation;
import android.graphics.Rect;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -73,6 +83,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.util.concurrent.Executor;
@@ -93,61 +104,107 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests";
private static final String TEST_PACKAGE_2 = "com.test.package.two";
private static final String CAMERA_ID_1 = "camera-1";
- private static final String CAMERA_ID_2 = "camera-2";
- private CameraManager mMockCameraManager;
- private Handler mMockHandler;
private AppCompatConfiguration mAppCompatConfiguration;
private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
private ActivityRecord mActivity;
- private Task mTask;
private ActivityRefresher mActivityRefresher;
@Before
public void setUp() throws Exception {
mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration;
spyOn(mAppCompatConfiguration);
- when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled())
- .thenReturn(true);
- when(mAppCompatConfiguration.isCameraCompatRefreshEnabled())
- .thenReturn(true);
+ when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true);
+ when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true);
when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
.thenReturn(true);
- mMockCameraManager = mock(CameraManager.class);
+ final CameraManager mockCameraManager = mock(CameraManager.class);
doAnswer(invocation -> {
mCameraAvailabilityCallback = invocation.getArgument(1);
return null;
- }).when(mMockCameraManager).registerAvailabilityCallback(
+ }).when(mockCameraManager).registerAvailabilityCallback(
any(Executor.class), any(CameraManager.AvailabilityCallback.class));
- when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
+ when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager);
mDisplayContent.setIgnoreOrientationRequest(true);
- mMockHandler = mock(Handler.class);
+ final Handler mockHandler = mock(Handler.class);
- when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ when(mockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
});
- mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
- mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
- CameraStateMonitor cameraStateMonitor =
- new CameraStateMonitor(mDisplayContent, mMockHandler);
- mCameraCompatFreeformPolicy =
- new CameraCompatFreeformPolicy(mDisplayContent, cameraStateMonitor,
- mActivityRefresher);
+ mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mockHandler);
+ final CameraStateMonitor cameraStateMonitor = new CameraStateMonitor(mDisplayContent,
+ mockHandler);
+ mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(mDisplayContent,
+ cameraStateMonitor, mActivityRefresher);
- setDisplayRotation(Surface.ROTATION_90);
+ setDisplayRotation(ROTATION_90);
mCameraCompatFreeformPolicy.start();
cameraStateMonitor.startListeningToCameraState();
}
@Test
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testIsCameraRunningAndWindowingModeEligible_featureDisabled_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testIsCameraRunningAndWindowingModeEligible_overrideDisabled_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testIsCameraRunningAndWindowingModeEligible_cameraNotRunning_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testFullscreen_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
doReturn(false).when(mActivity).inFreeformWindowingMode();
@@ -158,6 +215,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientationUnspecified_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
@@ -165,15 +224,19 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testNoCameraConnection_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
assertNotInCameraCompatMode();
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(Surface.ROTATION_0);
+ setDisplayRotation(ROTATION_0);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
@@ -181,9 +244,11 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(Surface.ROTATION_270);
+ setDisplayRotation(ROTATION_270);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
@@ -191,9 +256,11 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(Surface.ROTATION_0);
+ setDisplayRotation(ROTATION_0);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
@@ -201,9 +268,11 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(Surface.ROTATION_270);
+ setDisplayRotation(ROTATION_270);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
@@ -211,21 +280,28 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(Surface.ROTATION_270);
+ setDisplayRotation(ROTATION_270);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
+ /* lastLetterbox= */ false);
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ // Activity is letterboxed from the previous configuration change.
+ callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
+ /* lastLetterbox= */ true);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
assertActivityRefreshRequested(/* refreshRequested */ true);
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -235,25 +311,78 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
+ /* checkOrientation */ true));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
+ public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
assertTrue(mActivity.info
- .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT));
- assertFalse(mCameraCompatFreeformPolicy
- .shouldApplyFreeformTreatmentForCameraCompat(mActivity));
+ .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
+ assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
+ /* checkOrientation */ true));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ Configuration oldConfiguration = createConfiguration(/* letterbox= */ false);
+ Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
+ oldConfiguration));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
+ Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+
+ oldConfiguration.windowConfiguration.setDisplayRotation(0);
+ newConfiguration.windowConfiguration.setDisplayRotation(90);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
+ oldConfiguration));
}
@Test
- public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
+ Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+
+ oldConfiguration.windowConfiguration.setDisplayRotation(0);
+ newConfiguration.windowConfiguration.setDisplayRotation(0);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy
- .shouldApplyFreeformTreatmentForCameraCompat(mActivity));
+ assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
+ oldConfiguration));
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -268,6 +397,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
.thenReturn(false);
@@ -281,6 +412,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -293,6 +426,84 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
+ configureActivity(SCREEN_ORIENTATION_FULL_USER);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
+
+ assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
+ mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
+ /* delta= */ 0.001);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final float configAspectRatio = 1.5f;
+ mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
+
+ assertEquals(configAspectRatio,
+ mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
+ /* delta= */ 0.001);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final float configAspectRatio = 1.5f;
+ mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
+ doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .isOverrideMinAspectRatioForCameraEnabled();
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
+
+ assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
+ mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
+ /* delta= */ 0.001);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() throws
+ Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ setDisplayRotation(ROTATION_270);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ // This is a portrait rotation for a device with portrait natural orientation (most common,
+ // currently the only one supported).
+ assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() throws
+ Exception {
+ configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+ setDisplayRotation(ROTATION_0);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ // This is a landscape rotation for a device with portrait natural orientation (most common,
+ // currently the only one supported).
+ assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90);
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
}
@@ -304,7 +515,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
@Orientation int naturalOrientation, @WindowingMode int windowingMode) {
- mTask = new TaskBuilder(mSupervisor)
+ final Task task = new TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent)
.setWindowingMode(windowingMode)
.build();
@@ -314,7 +525,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
.setComponent(ComponentName.createRelative(mContext,
com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName()))
.setScreenOrientation(activityOrientation)
- .setTask(mTask)
+ .setTask(task)
.build();
spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
@@ -323,17 +534,17 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
- doReturn(true).when(mActivity).inFreeformWindowingMode();
+ doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity)
+ .inFreeformWindowingMode();
+ setupMockApplicationThread();
}
private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
- assertEquals(mode, mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .getFreeformCameraCompatMode());
+ assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity));
}
private void assertNotInCameraCompatMode() {
- assertEquals(CAMERA_COMPAT_FREEFORM_NONE, mActivity.mAppCompatController
- .getAppCompatCameraOverrides().getFreeformCameraCompatMode());
+ assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE);
}
private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
@@ -351,19 +562,26 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
- .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+ .scheduleTransactionItems(mActivity.app.getThread(),
refreshCallbackItem, resumeActivityItem);
}
private void callOnActivityConfigurationChanging(ActivityRecord activity) {
+ callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true,
+ /* lastLetterbox= */false);
+ }
+
+ private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew,
+ boolean lastLetterbox) {
mActivityRefresher.onActivityConfigurationChanging(activity,
- /* newConfig */ createConfiguration(/*letterbox=*/ true),
- /* lastReportedConfig */ createConfiguration(/*letterbox=*/ false));
+ /* newConfig */ createConfiguration(letterboxNew),
+ /* lastReportedConfig */ createConfiguration(lastLetterbox));
}
private Configuration createConfiguration(boolean letterbox) {
final Configuration configuration = new Configuration();
- Rect bounds = letterbox ? new Rect(300, 0, 700, 600) : new Rect(0, 0, 1000, 600);
+ Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600)
+ : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600);
configuration.windowConfiguration.setAppBounds(bounds);
return configuration;
}
@@ -377,9 +595,27 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
// case for most standard phones and tablets.
// TODO(b/365725400): handle landscape natural orientation.
displayInfo.logicalHeight = displayRotation % 180 == 0 ? 800 : 600;
- displayInfo.logicalWidth = displayRotation % 180 == 0 ? 600 : 800;
+ displayInfo.logicalWidth = displayRotation % 180 == 0 ? 600 : 800;
return displayInfo;
}).when(mDisplayContent.mWmService.mDisplayManagerInternal)
.getDisplayInfo(anyInt());
}
+
+ private void setupMockApplicationThread() {
+ IApplicationThread mockApplicationThread = mock(IApplicationThread.class);
+ spyOn(mActivity.app);
+ doReturn(mockApplicationThread).when(mActivity.app).getThread();
+ }
+
+ private void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int
+ expectedRotation) throws Exception {
+ final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor =
+ ArgumentCaptor.forClass(CompatibilityInfo.class);
+ verify(mActivity.app.getThread()).updatePackageCompatibilityInfo(eq(mActivity.packageName),
+ compatibilityInfoArgumentCaptor.capture());
+
+ final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue();
+ assertTrue(compatInfo.isOverrideDisplayRotationRequired());
+ assertEquals(expectedRotation, compatInfo.applicationDisplayRotation);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index ad80f82c8ea8..4810c7fc32d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -22,6 +22,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -137,6 +139,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
}
@Test
+ public void testOnCameraOpened_listenerAdded_cameraRegistersAsOpenedDuringTheCallback() {
+ mCameraStateMonitor.addCameraStateListener(mListener);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mListener.mIsCameraOpened);
+ }
+
+ @Test
public void testOnCameraOpened_cameraClosed_notifyCameraClosed() {
mCameraStateMonitor.addCameraStateListener(mListener);
// Listener returns true on `onCameraOpened`.
@@ -144,10 +154,21 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ assertEquals(1, mListener.mCheckCanCloseCounter);
assertEquals(1, mListener.mOnCameraClosedCounter);
}
@Test
+ public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() {
+ mCameraStateMonitor.addCameraStateListener(mListener);
+ // Listener returns true on `onCameraOpened`.
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ assertFalse(mListener.mIsCameraOpened);
+ }
+
+ @Test
public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() {
mCameraStateMonitor.addCameraStateListener(mListenerCannotClose);
// Listener returns true on `onCameraOpened`.
@@ -155,7 +176,8 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
- assertEquals(2, mListenerCannotClose.mOnCameraClosedCounter);
+ assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter);
+ assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter);
}
@Test
@@ -197,39 +219,49 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
CameraStateMonitor.CameraCompatStateListener {
int mOnCameraOpenedCounter = 0;
+ int mCheckCanCloseCounter = 0;
int mOnCameraClosedCounter = 0;
- private boolean mOnCameraClosedReturnValue = true;
+ boolean mIsCameraOpened;
+
+ private boolean mCheckCanCloseReturnValue = true;
/**
- * @param simulateUnsuccessfulCloseOnce When false, returns `true` on every
- * `onCameraClosed`. When true, returns `false` on the
- * first `onCameraClosed` callback, and `true on the
+ * @param simulateCannotCloseOnce When false, returns `true` on every
+ * `checkCanClose`. When true, returns `false` on the
+ * first `checkCanClose` callback, and `true on the
* subsequent calls. This fake implementation tests the
* retry mechanism in {@link CameraStateMonitor}.
*/
- FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) {
- mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce;
+ FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) {
+ mCheckCanCloseReturnValue = !simulateCannotCloseOnce;
}
@Override
- public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
mOnCameraOpenedCounter++;
+ mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity);
}
@Override
- public boolean onCameraClosed(@NonNull String cameraId) {
- mOnCameraClosedCounter++;
- boolean returnValue = mOnCameraClosedReturnValue;
+ public boolean canCameraBeClosed(@NonNull String cameraId) {
+ mCheckCanCloseCounter++;
+ final boolean returnValue = mCheckCanCloseReturnValue;
// If false, return false only the first time, so it doesn't fall in the infinite retry
// loop.
- mOnCameraClosedReturnValue = true;
+ mCheckCanCloseReturnValue = true;
return returnValue;
}
+ @Override
+ public void onCameraClosed() {
+ mOnCameraClosedCounter++;
+ mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity);
+ }
+
void resetCounters() {
mOnCameraOpenedCounter = 0;
+ mCheckCanCloseCounter = 0;
mOnCameraClosedCounter = 0;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 3ddf8da18d16..02ad9dbfda8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -123,7 +123,7 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
}
@Test
- public void testScheduleTransactionItemUnlocked() throws RemoteException {
+ public void testScheduleTransactionItemNow() throws RemoteException {
// Use non binder client to get non-recycled ClientTransaction.
mLifecycleManager.scheduleTransactionItemNow(mNonBinderClient, mTransactionItem);
@@ -133,12 +133,12 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
}
@Test
- public void testScheduleTransactionAndLifecycleItems() throws RemoteException {
+ public void testScheduleTransactionItems() throws RemoteException {
spyOn(mWms.mWindowPlacerLocked);
doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
// Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
+ mLifecycleManager.scheduleTransactionItems(mNonBinderClient, mTransactionItem,
mLifecycleItem);
assertEquals(1, mLifecycleManager.mPendingTransactions.size());
@@ -155,14 +155,15 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
}
@Test
- public void testScheduleTransactionAndLifecycleItems_shouldDispatchImmediately()
+ public void testScheduleTransactionItems_shouldDispatchImmediately()
throws RemoteException {
spyOn(mWms.mWindowPlacerLocked);
doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
// Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
- mLifecycleItem, true /* shouldDispatchImmediately */);
+ mLifecycleManager.scheduleTransactionItems(mNonBinderClient,
+ true /* shouldDispatchImmediately */,
+ mTransactionItem, mLifecycleItem);
verify(mLifecycleManager).scheduleTransaction(any());
assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
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 a5303b2eec5c..76b994d013f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -53,6 +53,7 @@ import android.media.projection.StopReason;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.SurfaceControl;
@@ -94,9 +95,11 @@ public class ContentRecorderTests extends WindowTestsBase {
private boolean mHandleAnisotropicDisplayMirroring = false;
@Before public void setUp() {
+ mDisplayInfo.type = Display.TYPE_VIRTUAL;
MockitoAnnotations.initMocks(this);
doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ doReturn(false).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
@@ -164,6 +167,25 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
+ public void testUpdateRecording_externalDisplayWithoutUserConfirmation() {
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testUpdateRecording_externalDisplayWithUserConfirmation() {
+ doReturn(true).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
index 44b69f18eb04..a0f4ae77452c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
@@ -39,6 +39,7 @@ import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.DisplayShape;
+import android.view.FrameRateCategoryRate;
import android.view.RoundedCorner;
import android.view.RoundedCorners;
import android.view.SurfaceControl.RefreshRateRange;
@@ -235,6 +236,12 @@ public class DeferredDisplayUpdaterDiffTest {
} else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) {
field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)});
field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)});
+ } else if (type.equals(FrameRateCategoryRate.class)) {
+ field.set(first, new FrameRateCategoryRate(16666667, 11111111));
+ field.set(second, new FrameRateCategoryRate(11111111, 8333333));
+ } else if (type.isArray() && type.getComponentType().equals(float.class)) {
+ field.set(first, new float[]{60.0f});
+ field.set(second, new float[]{120.0f});
} else {
throw new IllegalArgumentException("Field " + field
+ " is not supported by this test, please add implementation of setting "
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
new file mode 100644
index 000000000000..285a5e246e0c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.mockito.ArgumentMatchers.any;
+
+/** Robot for changing desktop windowing properties. */
+class DesktopWindowingRobot {
+ void allowEnterDesktopMode(boolean isAllowed) {
+ doReturn(isAllowed).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 1f3aa350b5b2..a30591ea7b15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -23,6 +23,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.utils.LastCallVerifier.lastCall;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +34,7 @@ import static org.mockito.Mockito.when;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -56,6 +58,7 @@ public class DimmerTests extends WindowTestsBase {
final SurfaceSession mSession = new SurfaceSession();
final SurfaceControl mHostControl = mock(SurfaceControl.class);
final SurfaceControl.Transaction mHostTransaction = spy(StubTransaction.class);
+ Rect mBounds = new Rect(10, 20, 30, 40);
MockSurfaceBuildingContainer(WindowManagerService wm) {
super(wm);
@@ -94,6 +97,11 @@ public class DimmerTests extends WindowTestsBase {
public SurfaceControl.Transaction getPendingTransaction() {
return mHostTransaction;
}
+
+ @Override
+ public Rect getBounds() {
+ return mBounds;
+ }
}
static class MockAnimationAdapterFactory extends DimmerAnimationHelper.AnimationAdapterFactory {
@@ -104,34 +112,63 @@ public class DimmerTests extends WindowTestsBase {
}
}
+ static class TestActivityEmbeddingMock {
+ Task mTask = mock(Task.class);
+ TaskFragment mLeft = mock(TaskFragment.class);
+ TaskFragment mRight = mock(TaskFragment.class);
+ Rect mTaskBounds = new Rect(10, 0, 50, 40);
+ Rect mLeftBounds = new Rect(10, 0, 30, 40);
+ Rect mRightBounds = new Rect(30, 0, 50, 40);
+
+ TestActivityEmbeddingMock() {
+ when(mTask.getBounds()).thenReturn(mTaskBounds);
+ when(mLeft.getBounds()).thenReturn(mLeftBounds);
+ when(mRight.getBounds()).thenReturn(mRightBounds);
+ when(mLeft.isEmbedded()).thenReturn(true);
+ when(mRight.isEmbedded()).thenReturn(true);
+ }
+
+ void pretendParentToTask(WindowState child) {
+ when(child.getTaskFragment()).thenReturn(mTask);
+ when(child.getTask()).thenReturn(mTask);
+ }
+
+ void pretendParentToRight(WindowState child) {
+ when(child.getTaskFragment()).thenReturn(mRight);
+ when(child.getTask()).thenReturn(mTask);
+ }
+ }
+
+ WindowState getMockDimmingContainer() {
+ WindowState window = mock(WindowState.class);
+ SurfaceControl surface = mock(SurfaceControl.class);
+ when(window.getSurfaceControl()).thenReturn(surface);
+ return window;
+ }
+
private Dimmer mDimmer;
private SurfaceControl.Transaction mTransaction;
+ MockSurfaceBuildingContainer mHost;
private WindowState mChild1;
private WindowState mChild2;
private static AnimationAdapter sTestAnimation;
@Before
public void setUp() throws Exception {
- MockSurfaceBuildingContainer host = new MockSurfaceBuildingContainer(mWm);
- mTransaction = host.getSyncTransaction();
-
- final SurfaceControl mControl1 = mock(SurfaceControl.class);
- final SurfaceControl mControl2 = mock(SurfaceControl.class);
+ mHost = new MockSurfaceBuildingContainer(mWm);
+ mTransaction = mHost.getSyncTransaction();
SurfaceAnimator animator = mock(SurfaceAnimator.class);
when(animator.getAnimation()).thenReturn(null);
- mChild1 = mock(WindowState.class);
- when(mChild1.getSurfaceControl()).thenReturn(mControl1);
-
- mChild2 = mock(WindowState.class);
- when(mChild2.getSurfaceControl()).thenReturn(mControl2);
+ mChild1 = getMockDimmingContainer();
+ mChild2 = getMockDimmingContainer();
- host.addChild(mChild1, 0);
- host.addChild(mChild2, 1);
+ mHost.addChild(mChild1, 0);
+ mHost.addChild(mChild2, 1);
sTestAnimation = spy(new MockAnimationAdapter());
- mDimmer = new Dimmer(host, new MockAnimationAdapterFactory());
+ mDimmer = new Dimmer(mHost, new MockAnimationAdapterFactory());
}
@Test
@@ -150,6 +187,63 @@ public class DimmerTests extends WindowTestsBase {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
+ public void testBoundsInActivityEmbeddingForWholeTask() {
+ final WindowState dimmingWindow = getMockDimmingContainer();
+ TestActivityEmbeddingMock embedding = new TestActivityEmbeddingMock();
+ embedding.pretendParentToRight(dimmingWindow);
+ when(embedding.mRight.isDimmingOnParentTask()).thenReturn(true);
+
+ mDimmer.adjustAppearance(dimmingWindow, 1, 1);
+ mDimmer.adjustPosition(dimmingWindow, dimmingWindow);
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(),
+ embedding.mTaskBounds.width(), embedding.mTaskBounds.height());
+ verify(mTransaction).setPosition(mDimmer.getDimLayer(), 0, 0);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
+ public void testBoundsInActivityEmbeddingForTaskFragmentOnly() {
+ final WindowState dimmingWindow = getMockDimmingContainer();
+ TestActivityEmbeddingMock embedding = new TestActivityEmbeddingMock();
+ embedding.pretendParentToRight(dimmingWindow);
+ when(embedding.mRight.isDimmingOnParentTask()).thenReturn(false);
+
+ mDimmer.adjustAppearance(dimmingWindow, 1, 1);
+ mDimmer.adjustPosition(dimmingWindow, dimmingWindow);
+ mDimmer.updateDims(mTransaction);
+ Rect expectedAbsoluteBounds = embedding.mRightBounds;
+ Rect expectedRelativeBounds = new Rect(expectedAbsoluteBounds);
+ expectedRelativeBounds.offset(-embedding.mTaskBounds.left, -embedding.mTaskBounds.top);
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(),
+ embedding.mRightBounds.width(), embedding.mRightBounds.height());
+ verify(mTransaction).setPosition(mDimmer.getDimLayer(),
+ expectedRelativeBounds.left, expectedRelativeBounds.top);
+ assertEquals(expectedAbsoluteBounds, mDimmer.getDimBounds());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
+ public void testDimBoundsAdaptToResizing() {
+ // First call with some generic bounds
+ mDimmer.adjustAppearance(mChild1, 0.5f, 1);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(),
+ mHost.getBounds().width(), mHost.getBounds().height());
+
+ // Change bounds
+ mHost.getBounds().left += 5;
+ mHost.getBounds().top += 6;
+ mDimmer.adjustAppearance(mChild1, 1, 1);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(),
+ mHost.getBounds().width(), mHost.getBounds().height());
+ }
+
+ @Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
final float alpha = 0.7f;
final int blur = 50;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 85cb1bcc01fb..9cbea2e2f0ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -83,7 +83,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFO
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.google.common.truth.Truth.assertThat;
@@ -601,12 +601,12 @@ public class DisplayContentTests extends WindowTestsBase {
TYPE_WALLPAPER, TYPE_APPLICATION);
// Verify not waiting for display without system decorations.
- doReturn(false).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(false).when(secondaryDisplay).isSystemDecorationsSupported();
assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify waiting for non-drawn windows on display with system decorations.
reset(secondaryDisplay);
- doReturn(true).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(true).when(secondaryDisplay).isSystemDecorationsSupported();
assertTrue(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify not waiting for drawn windows on display with system decorations.
@@ -1081,7 +1081,8 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayRotation dr = dc.getDisplayRotation();
spyOn(dr);
doReturn(false).when(dr).useDefaultSettingsProvider();
- final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setComponent(getUniqueComponentName(mContext.getPackageName())).build();
app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app);
assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
@@ -1608,8 +1609,10 @@ public class DisplayContentTests extends WindowTestsBase {
final ActivityRecord app = mAppWindow.mActivityRecord;
app.setVisible(false);
- mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(app);
+ app.setVisibleRequested(false);
+ registerTestTransitionPlayer();
+ mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ app.setVisibility(true);
final int newOrientation = getRotatedOrientation(mDisplayContent);
app.setRequestedOrientation(newOrientation);
@@ -1640,14 +1643,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(state.isSourceOrDefaultVisible(statusBarId, statusBars()),
rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
- final Rect outFrame = new Rect();
- final Rect outInsets = new Rect();
- final Rect outStableInsets = new Rect();
- final Rect outSurfaceInsets = new Rect();
- mAppWindow.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
- // The animation frames should not be rotated because display hasn't rotated.
- assertEquals(mDisplayContent.getBounds(), outFrame);
-
// The display should keep current orientation and the rotated configuration should apply
// to the activity.
assertEquals(config.orientation, mDisplayContent.getConfiguration().orientation);
@@ -1675,9 +1670,8 @@ public class DisplayContentTests extends WindowTestsBase {
// Launch another activity before the transition is finished.
final Task task2 = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
final ActivityRecord app2 = new ActivityBuilder(mAtm).setTask(task2)
- .setUseProcess(app.app).build();
- app2.setVisible(false);
- mDisplayContent.mOpeningApps.add(app2);
+ .setUseProcess(app.app).setVisible(false).build();
+ app2.setVisibility(true);
app2.setRequestedOrientation(newOrientation);
// The activity should share the same transform state as the existing one. The activity
@@ -1700,14 +1694,15 @@ public class DisplayContentTests extends WindowTestsBase {
assertTrue(mImeWindow.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM));
// The fixed rotation transform can only be finished when all animation finished.
- doReturn(false).when(app2).isAnimating(anyInt(), anyInt());
- mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token);
+ doReturn(false).when(app2).inTransition();
+ mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app2.token);
assertTrue(app.hasFixedRotationTransform());
assertTrue(app2.hasFixedRotationTransform());
// The display should be rotated after the launch is finished.
- doReturn(false).when(app).isAnimating(anyInt(), anyInt());
- mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+ app.setVisible(true);
+ doReturn(false).when(app).inTransition();
+ mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
mStatusBarWindow.finishSeamlessRotation(t);
mNavBarWindow.finishSeamlessRotation(t);
@@ -1725,7 +1720,7 @@ public class DisplayContentTests extends WindowTestsBase {
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
- doReturn(true).when(app).inTransitionSelfOrParent();
+ doReturn(true).when(app).inTransition();
// If the task contains a transition, this should be no-op.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
@@ -1735,7 +1730,7 @@ public class DisplayContentTests extends WindowTestsBase {
// The display should be unlikely to be in transition, but if it happens, the fixed
// rotation should proceed to finish because the activity/task level transition is finished.
doReturn(true).when(mDisplayContent).inTransition();
- doReturn(false).when(app).inTransitionSelfOrParent();
+ doReturn(false).when(app).inTransition();
// Although this notifies app instead of app2 that uses the fixed rotation, app2 should
// still finish the transform because there is no more transition event.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
@@ -1870,7 +1865,6 @@ public class DisplayContentTests extends WindowTestsBase {
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -1927,7 +1921,6 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -2268,25 +2261,25 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
- public void testForceDesktopMode() {
+ public void testIsPublicSecondaryDisplayWithDesktopModeForceEnabled() {
mWm.mForceDesktopModeOnExternalDisplays = true;
// Not applicable for default display
- assertFalse(mDefaultDisplay.forceDesktopMode());
+ assertFalse(mDefaultDisplay.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Not applicable for private secondary display.
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.flags = FLAG_PRIVATE;
final DisplayContent privateDc = createNewDisplay(displayInfo);
- assertFalse(privateDc.forceDesktopMode());
+ assertFalse(privateDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Applicable for public secondary display.
final DisplayContent publicDc = createNewDisplay();
- assertTrue(publicDc.forceDesktopMode());
+ assertTrue(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Make sure forceDesktopMode() is false when the force config is disabled.
mWm.mForceDesktopModeOnExternalDisplays = false;
- assertFalse(publicDc.forceDesktopMode());
+ assertFalse(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
}
@Test
@@ -2820,7 +2813,7 @@ public class DisplayContentTests extends WindowTestsBase {
verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId);
}
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void cameraCompatFreeformFlagEnabled_cameraCompatFreeformPolicyNotNull() {
doReturn(true).when(() ->
@@ -2829,7 +2822,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertTrue(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void cameraCompatFreeformFlagNotEnabled_cameraCompatFreeformPolicyIsNull() {
doReturn(true).when(() ->
@@ -2838,7 +2831,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
public void desktopWindowingFlagNotEnabled_cameraCompatFreeformPolicyIsNull() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 7a0961d8c306..1015651438c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -84,6 +84,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
// Disabling this call for most tests since it can override the systemUiFlags when called.
doNothing().when(mDisplayPolicy).updateSystemBarAttributes();
+ makeWindowVisible(mStatusBarWindow, mNavBarWindow);
updateDisplayFrames();
}
@@ -154,6 +155,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
};
addWindow(win);
win.getFrame().set(0, 0, 500, 100);
+ makeWindowVisible(win);
win.updateSourceFrame(win.getFrame());
mDisplayContent.getInsetsStateController().onPostLayout();
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 f32a234f3e40..27d46fc4e39e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,10 +16,8 @@
package com.android.server.wm;
-import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.InsetsSource.ID_IME;
-import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
import static android.view.Surface.ROTATION_0;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -40,7 +38,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -58,14 +55,11 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
-import android.view.DisplayShape;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
-import android.view.PrivacyIndicatorBounds;
import android.view.Surface;
import android.view.WindowInsets;
-import android.view.WindowInsets.Side;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -141,49 +135,49 @@ public class DisplayPolicyTests extends WindowTestsBase {
// If everything is null, return null.
assertNull(null, DisplayPolicy.chooseNavigationColorWindowLw(
- null, null, NAV_BAR_BOTTOM));
+ null, null, true));
// If no IME windows, return candidate window.
assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
- candidate, null, NAV_BAR_BOTTOM));
+ candidate, null, true));
assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingImTarget, null, NAV_BAR_BOTTOM));
+ dimmingImTarget, null, true));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingNonImTarget, null, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, null, true));
// If IME is not visible, return candidate window.
assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw(
- null, invisibleIme, NAV_BAR_BOTTOM));
+ null, invisibleIme, true));
assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
- candidate, invisibleIme, NAV_BAR_BOTTOM));
+ candidate, invisibleIme, true));
assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingImTarget, invisibleIme, NAV_BAR_BOTTOM));
+ dimmingImTarget, invisibleIme, true));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingNonImTarget, invisibleIme, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, invisibleIme, true));
// If IME is visible, return candidate when the candidate window is not dimming.
assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- null, visibleIme, NAV_BAR_BOTTOM));
+ null, visibleIme, true));
assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- candidate, visibleIme, NAV_BAR_BOTTOM));
+ candidate, visibleIme, true));
// If IME is visible and the candidate window is dimming, checks whether the dimming window
// can be IME tartget or not.
assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
+ dimmingImTarget, visibleIme, true));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, visibleIme, true));
// Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color
// window.
assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw(
- null, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ null, imeNonDrawNavBar, true));
assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
- candidate, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ candidate, imeNonDrawNavBar, true));
assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ dimmingImTarget, imeNonDrawNavBar, true));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, imeNonDrawNavBar, true));
}
@Test
@@ -196,32 +190,32 @@ public class DisplayPolicyTests extends WindowTestsBase {
final WindowState nonDrawBarIme = createInputMethodWindow(true, false, false);
assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
- drawBarWin, null, NAV_BAR_BOTTOM));
+ drawBarWin, null, true));
assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
- null, null, NAV_BAR_BOTTOM));
+ null, null, true));
assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
- nonDrawBarWin, null, NAV_BAR_BOTTOM));
+ nonDrawBarWin, null, true));
assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
- drawBarWin, visibleIme, NAV_BAR_BOTTOM));
+ drawBarWin, visibleIme, true));
assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
- null, visibleIme, NAV_BAR_BOTTOM));
+ null, visibleIme, true));
assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
- nonDrawBarWin, visibleIme, NAV_BAR_BOTTOM));
+ nonDrawBarWin, visibleIme, true));
assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
- drawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+ drawBarWin, invisibleIme, true));
assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
- null, invisibleIme, NAV_BAR_BOTTOM));
+ null, invisibleIme, true));
assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
- nonDrawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+ nonDrawBarWin, invisibleIme, true));
assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
- drawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+ drawBarWin, nonDrawBarIme, true));
assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
- null, nonDrawBarIme, NAV_BAR_BOTTOM));
+ null, nonDrawBarIme, true));
assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
- nonDrawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+ nonDrawBarWin, nonDrawBarIme, true));
}
@SetupWindows(addWindows = W_NAVIGATION_BAR)
@@ -413,15 +407,25 @@ public class DisplayPolicyTests extends WindowTestsBase {
@Test
public void testUpdateDisplayConfigurationByDecor() {
- if (Flags.insetsDecoupledConfiguration()) {
- // No configuration update when flag enables.
- return;
- }
doReturn(NO_CUTOUT).when(mDisplayContent).calculateDisplayCutoutForRotation(anyInt());
final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent);
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
final DisplayInfo di = mDisplayContent.getDisplayInfo();
final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp;
+ if (Flags.insetsDecoupledConfiguration()) {
+ // No configuration update when flag enables.
+ assertFalse(displayPolicy.updateDecorInsetsInfo());
+ assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
+ di.logicalHeight, di.logicalWidth).mOverrideConfigInsets.bottom);
+
+ final int barHeight = 2 * NAV_BAR_HEIGHT;
+ navbar.mAttrs.providedInsets[0].setInsetsSize(Insets.of(0, 0, 0, barHeight));
+ assertFalse(displayPolicy.updateDecorInsetsInfo());
+ assertEquals(barHeight, displayPolicy.getDecorInsetsInfo(di.rotation,
+ di.logicalHeight, di.logicalWidth).mOverrideConfigInsets.bottom);
+ return;
+ }
+
assertTrue(navbar.providesDisplayDecorInsets() && displayPolicy.updateDecorInsetsInfo());
assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
di.logicalWidth, di.logicalHeight).mConfigInsets.bottom);
@@ -486,50 +490,13 @@ public class DisplayPolicyTests extends WindowTestsBase {
di.logicalHeight).mNonDecorInsets.bottom);
}
- @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
- @Test
- public void testImeMinimalSourceFrame() {
- Assume.assumeFalse("Behavior no longer needed with ENABLE_HIDE_IME_CAPTION_BAR",
- ENABLE_HIDE_IME_CAPTION_BAR);
-
- final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-
- WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs;
- displayPolicy.addWindowLw(mNavBarWindow, attrs);
- mNavBarWindow.setRequestedSize(attrs.width, attrs.height);
- mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
- final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
- mImeWindow.mAboveInsetsState.set(state);
- mDisplayContent.mDisplayFrames = new DisplayFrames(
- state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
- DisplayShape.NONE);
-
- mDisplayContent.setInputMethodWindowLocked(mImeWindow);
- mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
- mImeWindow.mGivenContentInsets.set(0, displayInfo.logicalHeight, 0, 0);
- mImeWindow.getControllableInsetProvider().setServerVisible(true);
-
- displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
- displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
-
- final InsetsSource imeSource = state.peekSource(ID_IME);
- final InsetsSource navBarSource = state.peekSource(
- mNavBarWindow.getControllableInsetProvider().getSource().getId());
-
- assertNotNull(imeSource);
- assertNotNull(navBarSource);
- assertFalse(imeSource.getFrame().isEmpty());
- assertFalse(navBarSource.getFrame().isEmpty());
- assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
- }
-
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsGivenContentFrame() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
+ makeWindowVisible(mImeWindow);
mImeWindow.getControllableInsetProvider().setServerVisible(true);
mImeWindow.mGivenContentInsets.set(0, 10, 0, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 8cf593fd21db..23c767c87e4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -51,6 +51,7 @@ import static org.mockito.Mockito.times;
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -544,39 +545,35 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
}
@Test
- public void testIsCameraActiveWhenCallbackInvokedNoMultiWindow_returnTrue() {
+ public void testShouldCameraCompatControlOrientationWhenInvokedNoMultiWindow_returnTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertTrue(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
@Test
- public void testIsCameraActiveWhenCallbackNotInvokedNoMultiWindow_returnFalse() {
+ public void testShouldCameraCompatControlOrientationWhenNotInvokedNoMultiWindow_returnFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertFalse(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
@Test
- public void testIsCameraActiveWhenCallbackNotInvokedMultiWindow_returnFalse() {
+ public void testShouldCameraCompatControlOrientationWhenNotInvokedMultiWindow_returnFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
when(mActivity.inMultiWindowMode()).thenReturn(true);
- assertFalse(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertFalse(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
@Test
- public void testIsCameraActiveWhenCallbackInvokedMultiWindow_returnFalse() {
+ public void testShouldCameraCompatControlOrientationWhenInvokedMultiWindow_returnFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
when(mActivity.inMultiWindowMode()).thenReturn(true);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertFalse(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
private void configureActivity(@ScreenOrientation int activityOrientation) {
@@ -596,6 +593,11 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
.setTask(mTask)
.build();
+ spyOn(mActivity.info.applicationInfo);
+ // Disable for camera compat.
+ doReturn(false).when(mActivity.info.applicationInfo).isChangeEnabled(
+ ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
+
spyOn(mActivity.mAtmService.getLifecycleManager());
spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
@@ -618,7 +620,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
- .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+ .scheduleTransactionItems(mActivity.app.getThread(),
refreshCallbackItem, resumeActivityItem);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
index 4557df0e9c98..266ffffabf15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -40,6 +40,9 @@ import org.junit.Test;
@Presubmit
public class DisplayRotationCoordinatorTests {
+ private static final int FIRST_DISPLAY_ID = 1;
+ private static final int SECOND_DISPLAY_ID = 2;
+
@NonNull
private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
@@ -50,22 +53,45 @@ public class DisplayRotationCoordinatorTests {
}
@Test (expected = UnsupportedOperationException.class)
- public void testSecondRegistrationWithoutRemovingFirst() {
+ public void testSecondRegistrationWithoutRemovingFirstWhenDifferentDisplay() {
Runnable callback1 = mock(Runnable.class);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
}
@Test
+ public void testSecondRegistrationWithoutRemovingFirstWhenSameDisplay() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
+ public void testRemoveIncorrectRegistration() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback2);
+ assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+
+ // FIRST_DISPLAY_ID is still able to register another callback because the previous
+ // removal should not have succeeded.
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
public void testSecondRegistrationAfterRemovingFirst() {
Runnable callback1 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback1);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
verify(callback2).run();
@@ -75,7 +101,7 @@ public class DisplayRotationCoordinatorTests {
@Test
public void testRegisterThenDefaultDisplayRotationChanged() {
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
verify(callback, never()).run();
@@ -88,7 +114,7 @@ public class DisplayRotationCoordinatorTests {
public void testDefaultDisplayRotationChangedThenRegister() {
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
verify(callback).run();
assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index c8fc4822259e..63973345b5fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -20,16 +20,19 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
import android.platform.test.annotations.Presubmit;
import android.view.Surface;
@@ -54,6 +57,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas
private DisplayRotationImmersiveAppCompatPolicy mPolicy;
+ private DisplayRotation mMockDisplayRotation;
private AppCompatConfiguration mMockAppCompatConfiguration;
private ActivityRecord mMockActivityRecord;
private Task mMockTask;
@@ -98,6 +102,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_90)).thenReturn(true);
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_180)).thenReturn(false);
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_270)).thenReturn(true);
+ mMockDisplayRotation = mockDisplayRotation;
return mockDisplayRotation;
}
@@ -196,6 +201,24 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas
}
@Test
+ public void testDeferOrientationUpdate() {
+ assertFalse(mPolicy.deferOrientationUpdate());
+
+ doReturn(SCREEN_ORIENTATION_UNSPECIFIED).when(mMockDisplayRotation).getLastOrientation();
+ final WindowOrientationListener orientationListener = mock(WindowOrientationListener.class);
+ doReturn(Surface.ROTATION_90).when(orientationListener).getProposedRotation();
+ doReturn(orientationListener).when(mMockDisplayRotation).getOrientationListener();
+ spyOn(mDisplayContent.mTransitionController);
+ doReturn(true).when(mDisplayContent.mTransitionController)
+ .hasTransientLaunch(mDisplayContent);
+
+ assertTrue(mPolicy.deferOrientationUpdate());
+ mDisplayContent.mTransitionController.mStateValidators.getFirst().run();
+
+ verify(mWm).updateRotation(false, false);
+ }
+
+ @Test
public void testRotationChoiceEnforcedOnly_nullTopRunningActivity_lockNotEnforced() {
when(mDisplayContent.topRunningActivity()).thenReturn(null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index eacb8e9d628d..c016c5ead23c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -70,7 +69,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-
+ private static final float HI_REFRESH_RATE = 90;
+ private static final float MID_REFRESH_RATE = 70;
+ private static final float LOW_REFRESH_RATE = 60;
WindowState createWindow(String name) {
WindowState window = createWindow(null, TYPE_APPLICATION, name);
when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
@@ -83,14 +84,16 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
DisplayInfo di = new DisplayInfo(mDisplayInfo);
Mode defaultMode = di.getDefaultMode();
Mode hiMode = new Mode(1,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90);
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), HI_REFRESH_RATE);
Mode midMode = new Mode(2,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70);
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), MID_REFRESH_RATE);
Mode lowMode = new Mode(LOW_MODE_ID,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60);
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
di.supportedModes = new Mode[] { hiMode, midMode };
di.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
+ di.supportedRefreshRates = new float[] {HI_REFRESH_RATE, MID_REFRESH_RATE,
+ LOW_REFRESH_RATE};
di.defaultModeId = 1;
mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
@@ -190,14 +193,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
eq(appWindow.getSurfaceControl()), anyFloat(),
eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -226,14 +224,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
eq(appWindow.getSurfaceControl()), anyFloat(),
eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -288,14 +281,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -352,13 +340,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index b26c267768a7..ee56210e278d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -41,8 +41,13 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import android.app.StatusBarManager;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.os.Binder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
@@ -52,6 +57,7 @@ import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -95,6 +101,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void testControlsForDispatch_freeformTaskVisible() {
addStatusBar();
addNavigationBar();
@@ -108,6 +115,37 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void testControlsForDispatch_fullscreenFreeformTaskVisible() {
+ addStatusBar();
+ addNavigationBar();
+
+ final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ win.setBounds(new Rect());
+ final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+ // The freeform (w/fullscreen bounds) app window can control both system bars.
+ assertNotNull(controls);
+ assertEquals(2, controls.length);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void testControlsForDispatch_nonFullscreenFreeformTaskVisible() {
+ addStatusBar();
+ addNavigationBar();
+
+ final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ win.getTask().setBounds(new Rect(1, 1, 10, 10));
+ final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+ // The freeform (but not fullscreen bounds) app window must not control any system bars.
+ assertNull(controls);
+ }
+
+ @Test
public void testControlsForDispatch_forceStatusBarVisible() {
addStatusBar().mAttrs.forciblyShownTypes |= statusBars();
addNavigationBar();
@@ -489,6 +527,59 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertTrue(win1.getWindowFrames().hasInsetsChanged());
}
+ /**
+ * This test verifies that after setting {@link WindowContainer#mExcludeInsetsTypes}, the IME
+ * insets have a height of zero (applied in {@link InsetsPolicy#adjustVisibilityForIme}).
+ */
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testExcludeImeInsets() {
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final InsetsSource imeSource = new InsetsSource(ID_IME, ime());
+ imeSource.setVisible(true);
+ mImeWindow.mHasSurface = true;
+
+ final WindowState win = addWindow(TYPE_APPLICATION, "win1");
+ win.setRequestedVisibleTypes(0, ime());
+
+ win.mAboveInsetsState.addSource(imeSource);
+ win.mHasSurface = true;
+
+ DisplayContentTests.performLayout(mDisplayContent);
+ // IME should cover half of the app's window
+ final var winFrame = win.getFrame();
+ imeSource.setFrame(winFrame.left, winFrame.bottom / 2, winFrame.right, winFrame.bottom);
+ imeSource.setVisibleFrame(imeSource.getFrame());
+ DisplayContentTests.performLayout(mDisplayContent);
+
+ assertTrue(mImeWindow.isVisible());
+ assertTrue(win.isVisible());
+
+ displayPolicy.beginPostLayoutPolicyLw();
+ displayPolicy.applyPostLayoutPolicyLw(win, win.mAttrs, null, null);
+ displayPolicy.finishPostLayoutPolicyLw();
+
+ final var imeInsetsShown = win.getInsetsState().calculateInsets(win.getFrame(), ime(),
+ true);
+ assertEquals(new Rect(0, 0, 0, winFrame.bottom / 2), imeInsetsShown.toRect());
+
+
+ // Now we're setting the excludedInsetsTypes for the IME. The IME is still showing, but
+ // in this case, InsetsPolicy#adjustVisibilityForIme will override and dispatch IME
+ // insets with zero height.
+ win.setExcludeInsetsTypes(ime());
+
+ displayPolicy.beginPostLayoutPolicyLw();
+ displayPolicy.applyPostLayoutPolicyLw(win, win.mAttrs, null, null);
+ displayPolicy.finishPostLayoutPolicyLw();
+
+ final var imeInsetsHidden = win.getInsetsState().calculateInsets(win.getFrame(), ime(),
+ true);
+ assertEquals(Insets.NONE, imeInsetsHidden);
+ }
+
+
private WindowState addNavigationBar() {
final Binder owner = new Binder();
final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index f48ba65217e2..79967b861ea5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -189,7 +189,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
assertNull(mProvider.getLeash(target));
// Set the leash to be ready for dispatching.
- mProvider.mIsLeashReadyForDispatching = true;
+ mProvider.mIsLeashInitialized = true;
assertNotNull(mProvider.getLeash(target));
// We do have fake control for the fake control target, but that has no leash.
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index d0d7c06bd706..66a66a1e358b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -291,6 +291,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ makeWindowVisible(statusBar);
+
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 1be61c36f272..66d7963946b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -494,6 +494,21 @@ public class LaunchParamsPersisterTests extends WindowTestsBase {
assertTrue("Result should be empty.", mResult.isEmpty());
}
+ @Test
+ public void testAbortsLoadingWhenUserCleansUpBeforeLoadingFinishes() {
+ mTarget.saveTask(mTestTask);
+ mPersisterQueue.flush();
+
+ final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor,
+ mUserFolderGetter);
+ target.onSystemReady();
+ target.onUnlockUser(TEST_USER_ID);
+ target.onCleanupUser(TEST_USER_ID);
+
+ target.getLaunchParams(mTestTask, null, mResult);
+ assertTrue("Result should be empty.", mResult.isEmpty());
+ }
+
private static boolean deleteRecursively(File file) {
boolean result = true;
if (file.isDirectory()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
new file mode 100644
index 000000000000..7e1de4762681
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.testutils.MockitoUtilsKt.eq;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.InputConfig;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.InputWindowHandle;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+import com.android.server.testutils.StubTransaction;
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Test class for {@link Letterbox}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:LetterboxAttachInputTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class LetterboxAttachInputTest extends WindowTestsBase {
+
+ private Letterbox mLetterbox;
+ private LetterboxTest.SurfaceControlMocker mSurfaces;
+
+ @Before
+ public void setUp() throws Exception {
+ mSurfaces = new LetterboxTest.SurfaceControlMocker();
+ AppCompatLetterboxOverrides letterboxOverrides = mock(AppCompatLetterboxOverrides.class);
+ doReturn(false).when(letterboxOverrides).shouldLetterboxHaveRoundedCorners();
+ doReturn(Color.valueOf(Color.BLACK)).when(letterboxOverrides)
+ .getLetterboxBackgroundColor();
+ doReturn(false).when(letterboxOverrides).hasWallpaperBackgroundForLetterbox();
+ doReturn(0).when(letterboxOverrides).getLetterboxWallpaperBlurRadiusPx();
+ doReturn(0.5f).when(letterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
+ mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
+ mock(AppCompatReachabilityPolicy.class), letterboxOverrides,
+ () -> mock(SurfaceControl.class));
+ mTransaction = spy(StubTransaction.class);
+ }
+
+ @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testSurface_createdHasSlipperyInput_scrollingFromLetterboxDisabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+
+ attachInput();
+ applySurfaceChanges();
+
+ assertNotNull(mSurfaces.top);
+ ArgumentCaptor<InputWindowHandle> handleCaptor =
+ ArgumentCaptor.forClass(InputWindowHandle.class);
+ verify(mTransaction).setInputWindowInfo(eq(mSurfaces.top), handleCaptor.capture());
+ InputWindowHandle capturedHandle = handleCaptor.getValue();
+ assertTrue((capturedHandle.inputConfig & InputConfig.SLIPPERY) != 0);
+ assertFalse((capturedHandle.inputConfig & InputConfig.SPY) != 0);
+ }
+
+ @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testInputSurface_notCreated_scrollingFromLetterboxDisabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+
+ attachInput();
+ applySurfaceChanges();
+
+ assertNull(mSurfaces.topInput);
+ }
+
+ @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testSurface_createdHasNoInput_scrollingFromLetterboxEnabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+
+ attachInput();
+ applySurfaceChanges();
+
+ assertNotNull(mSurfaces.top);
+ verify(mTransaction, never()).setInputWindowInfo(eq(mSurfaces.top), any());
+
+ }
+
+ @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testInputSurface_createdHasSpyInput_scrollingFromLetterboxEnabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+
+ attachInput();
+ applySurfaceChanges();
+
+ assertNotNull(mSurfaces.topInput);
+ ArgumentCaptor<InputWindowHandle> handleCaptor =
+ ArgumentCaptor.forClass(InputWindowHandle.class);
+ verify(mTransaction).setInputWindowInfo(eq(mSurfaces.topInput), handleCaptor.capture());
+ InputWindowHandle capturedHandle = handleCaptor.getValue();
+ assertTrue((capturedHandle.inputConfig & InputConfig.SPY) != 0);
+ assertFalse((capturedHandle.inputConfig & InputConfig.SLIPPERY) != 0);
+ }
+
+ @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testInputSurfaceOrigin_applied_scrollingFromLetterboxEnabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+
+ attachInput();
+ applySurfaceChanges();
+
+ verify(mTransaction).setPosition(mSurfaces.topInput, -1000, -2000);
+ }
+
+ @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testInputSurfaceOrigin_changeCausesReapply_scrollingFromLetterboxEnabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+
+ attachInput();
+ applySurfaceChanges();
+ clearInvocations(mTransaction);
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
+
+ assertTrue(mLetterbox.needsApplySurfaceChanges());
+
+ applySurfaceChanges();
+
+ verify(mTransaction).setPosition(mSurfaces.topInput, 0, 0);
+ }
+
+ private void applySurfaceChanges() {
+ mLetterbox.applySurfaceChanges(/* syncTransaction */ mTransaction,
+ /* pendingTransaction */ mTransaction);
+ }
+
+ private void attachInput() {
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ final WindowToken windowToken = createTestWindowToken(0, mDisplayContent);
+ WindowState windowState = createWindowState(attrs, windowToken);
+ mLetterbox.attachInput(windowState);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 400fe8b05526..0baa5171a541 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -33,14 +33,19 @@ import static org.mockito.Mockito.when;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import com.android.server.testutils.StubTransaction;
+import com.android.window.flags.Flags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
@@ -56,9 +61,12 @@ import java.util.function.Supplier;
@Presubmit
public class LetterboxTest {
- Letterbox mLetterbox;
- SurfaceControlMocker mSurfaces;
- SurfaceControl.Transaction mTransaction;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Letterbox mLetterbox;
+ private SurfaceControlMocker mSurfaces;
+ private SurfaceControl.Transaction mTransaction;
private SurfaceControl mParentSurface = mock(SurfaceControl.class);
private AppCompatLetterboxOverrides mLetterboxOverrides;
@@ -183,6 +191,38 @@ public class LetterboxTest {
verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000);
}
+ @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testSurface_created_scrollingFromLetterboxDisabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ applySurfaceChanges();
+ assertNotNull(mSurfaces.top);
+ }
+
+ @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testInputSurface_notCreated_scrollingFromLetterboxDisabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ applySurfaceChanges();
+ assertNull(mSurfaces.topInput);
+ }
+
+ @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testSurface_created_scrollingFromLetterboxEnabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ applySurfaceChanges();
+ assertNotNull(mSurfaces.top);
+ }
+
+ @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
+ @Test
+ public void testInputSurface_notCreated_notAttachedInputAndScrollingFromLetterboxEnabled() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ applySurfaceChanges();
+ assertNull(mSurfaces.topInput);
+ }
+
@Test
public void testApplySurfaceChanges_setColor() {
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
@@ -283,15 +323,11 @@ public class LetterboxTest {
/* pendingTransaction */ mTransaction);
}
- class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> {
- private SurfaceControl.Builder mLeftBuilder;
- public SurfaceControl left;
+ static class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> {
private SurfaceControl.Builder mTopBuilder;
public SurfaceControl top;
- private SurfaceControl.Builder mRightBuilder;
- public SurfaceControl right;
- private SurfaceControl.Builder mBottomBuilder;
- public SurfaceControl bottom;
+ private SurfaceControl.Builder mTopInputBuilder;
+ public SurfaceControl topInput;
private SurfaceControl.Builder mFullWindowSurfaceBuilder;
public SurfaceControl fullWindowSurface;
@@ -300,32 +336,24 @@ public class LetterboxTest {
final SurfaceControl.Builder builder = mock(SurfaceControl.Builder.class,
InvocationOnMock::getMock);
when(builder.setName(anyString())).then((i) -> {
- if (((String) i.getArgument(0)).contains("left")) {
- mLeftBuilder = (SurfaceControl.Builder) i.getMock();
- } else if (((String) i.getArgument(0)).contains("top")) {
+ if (((String) i.getArgument(0)).contains("Letterbox - top")) {
mTopBuilder = (SurfaceControl.Builder) i.getMock();
- } else if (((String) i.getArgument(0)).contains("right")) {
- mRightBuilder = (SurfaceControl.Builder) i.getMock();
- } else if (((String) i.getArgument(0)).contains("bottom")) {
- mBottomBuilder = (SurfaceControl.Builder) i.getMock();
- } else if (((String) i.getArgument(0)).contains("fullWindow")) {
+ } else if (((String) i.getArgument(0)).contains("Letterbox - fullWindow")) {
mFullWindowSurfaceBuilder = (SurfaceControl.Builder) i.getMock();
+ } else if (((String) i.getArgument(0)).contains("LetterboxInput - top")) {
+ mTopInputBuilder = (SurfaceControl.Builder) i.getMock();
}
return i.getMock();
});
doAnswer((i) -> {
final SurfaceControl control = mock(SurfaceControl.class);
- if (i.getMock() == mLeftBuilder) {
- left = control;
- } else if (i.getMock() == mTopBuilder) {
+ if (i.getMock() == mTopBuilder) {
top = control;
- } else if (i.getMock() == mRightBuilder) {
- right = control;
- } else if (i.getMock() == mBottomBuilder) {
- bottom = control;
} else if (i.getMock() == mFullWindowSurfaceBuilder) {
fullWindowSurface = control;
+ } else if (i.getMock() == mTopInputBuilder) {
+ topInput = control;
}
return control;
}).when(builder).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 35cff7a7a324..1a4cc55af450 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -26,8 +26,8 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.LogLevel;
@@ -51,13 +51,13 @@ public class ProtoLogIntegrationTest {
public void testProtoLogToolIntegration() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
runWith(mockedProtoLog, this::testProtoLog);
- verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
+ verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(WmProtoLogGroups.TEST_GROUP),
anyInt(), eq(0b0010010111),
eq(new Object[]{true, 1L, 2L, 0.3, "ok"}));
}
private void testProtoLog() {
- ProtoLog.e(ProtoLogGroup.TEST_GROUP,
+ ProtoLog.e(WmProtoLogGroups.TEST_GROUP,
"Test completed successfully: %b %d %x %f %% %s",
true, 1, 2, 0.3, "ok");
}
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 e0344d73f540..25b9f4b8035b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -432,6 +432,34 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
+ public void testAddTaskCompatibleWindowingMode_withFreeformAndFullscreen_expectRemove() {
+ Task task1 = createTaskBuilder(".Task1")
+ .setTaskId(1)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ doReturn(WINDOWING_MODE_FREEFORM).when(task1).getWindowingMode();
+ mRecentTasks.add(task1);
+ mCallbacksRecorder.clear();
+
+ Task task2 = createTaskBuilder(".Task1")
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ assertEquals(WINDOWING_MODE_FULLSCREEN, task2.getWindowingMode());
+ mRecentTasks.add(task2);
+
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertThat(mCallbacksRecorder.mAdded).contains(task2);
+ assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+ assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+ assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+
+ TaskChangeNotificationController controller =
+ mAtm.getTaskChangeNotificationController();
+ verify(controller, times(1)).notifyRecentTaskRemovedForAddTask(task1.mTaskId);
+ }
+
+ @Test
public void testAddTaskIncompatibleWindowingMode_expectNoRemove() {
Task task1 = createTaskBuilder(".Task1")
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
@@ -473,24 +501,6 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- public void testAppendOrganizedChildTaskInfo() {
- final Task root = createTaskBuilder(".CreatedByOrganizerRoot").build();
- root.mCreatedByOrganizer = true;
- // Add organized and non-organized child.
- final Task child1 = createTaskBuilder(".Task1").setParentTask(root).build();
- final Task child2 = createTaskBuilder(".Task2").setParentTask(root).build();
- doReturn(true).when(child1).isOrganized();
- doReturn(false).when(child2).isOrganized();
- mRecentTasks.add(root);
-
- // Make sure only organized child will be appended.
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final List<RecentTaskInfo> childrenTaskInfos = infos.get(0).childrenTaskInfos;
- assertEquals(childrenTaskInfos.size(), 1);
- assertEquals(childrenTaskInfos.get(0).taskId, child1.mTaskId);
- }
-
- @Test
public void testAddTasksHomeClearUntrackedTasks_expectFinish() {
// There may be multiple tasks with the same base intent by flags (FLAG_ACTIVITY_NEW_TASK |
// FLAG_ACTIVITY_MULTIPLE_TASK). If the previous task is still active, it should be removed
@@ -707,13 +717,13 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents() {
testVisibleTasks_excludedFromRecents_internal();
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_withRefactorFlag() {
testVisibleTasks_excludedFromRecents_internal();
}
@@ -757,13 +767,13 @@ public class RecentTasksTest extends WindowTestsBase {
@Test
@Ignore("b/342627272")
- @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() {
testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal();
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() {
testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal();
}
@@ -806,13 +816,13 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() {
testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal();
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() {
testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal();
}
@@ -1397,46 +1407,6 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- public void testLastSnapshotData_snapshotSaved() {
- final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), new Point(80, 80));
- final Task task1 = createTaskBuilder(".Task").build();
- task1.onSnapshotChanged(snapshot);
-
- mRecentTasks.add(task1);
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- infos.get(0).lastSnapshotData;
- assertTrue(lastSnapshotData.taskSize.equals(100, 100));
- assertTrue(lastSnapshotData.bufferSize.equals(80, 80));
- }
-
- @Test
- public void testLastSnapshotData_noBuffer() {
- final Task task1 = createTaskBuilder(".Task").build();
- final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), null);
- task1.onSnapshotChanged(snapshot);
-
- mRecentTasks.add(task1);
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- infos.get(0).lastSnapshotData;
- assertTrue(lastSnapshotData.taskSize.equals(100, 100));
- assertNull(lastSnapshotData.bufferSize);
- }
-
- @Test
- public void testLastSnapshotData_notSet() {
- final Task task1 = createTaskBuilder(".Task").build();
-
- mRecentTasks.add(task1);
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- infos.get(0).lastSnapshotData;
- assertNull(lastSnapshotData.taskSize);
- assertNull(lastSnapshotData.bufferSize);
- }
-
- @Test
public void testCreateRecentTaskInfo_detachedTask() {
final Task task = createTaskBuilder(".Task").build();
final ComponentName componentName = getUniqueComponentName();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 3fa38bfe7185..73e5f58fa7e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,14 +21,11 @@ import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManager;
@@ -36,7 +33,6 @@ import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.view.Display.Mode;
import android.view.Surface;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.test.filters.SmallTest;
@@ -105,6 +101,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
mDisplayInfo.supportedModes = new Mode[] { hiMode, midMode };
mDisplayInfo.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
+ mDisplayInfo.supportedRefreshRates = new float[] {HI_REFRESH_RATE, MID_REFRESH_RATE,
+ LOW_REFRESH_RATE};
mDisplayInfo.defaultModeId = HI_MODE_ID;
mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
}
@@ -274,7 +272,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
}
@Test
- public void testAnimatingAppOverridePreferredModeId() {
+ public void testInsetsAnimationAppOverridePreferredModeId() {
final WindowState overrideWindow = createWindow("overrideWindow");
overrideWindow.mAttrs.packageName = "com.android.test";
overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
@@ -285,37 +283,16 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- if (explicitRefreshRateHints()) {
- return;
- }
- overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
- overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
+ overrideWindow.notifyInsetsAnimationRunningStateChanged(true);
+ assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- // Use default mode if it is animating by shell transition.
- overrideWindow.mActivityRecord.mSurfaceAnimator.cancelAnimation();
- registerTestTransitionPlayer();
- final Transition transition = overrideWindow.mTransitionController.createTransition(
- WindowManager.TRANSIT_OPEN);
- transition.collect(overrideWindow.mActivityRecord);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-
- // If there will be display size change when switching from preferred mode to default mode,
- // then keep the current preferred mode during animating.
- mDisplayInfo = spy(mDisplayInfo);
- final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
- doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
- mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
- assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
}
@Test
- public void testAnimatingAppOverridePreferredRefreshRate() {
+ public void testInsetsAnimationAppOverridePreferredRefreshRate() {
final WindowState overrideWindow = createWindow("overrideWindow");
overrideWindow.mAttrs.packageName = "com.android.test";
overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
@@ -326,12 +303,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- if (explicitRefreshRateHints()) {
- return;
- }
- overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
- overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
+ overrideWindow.notifyInsetsAnimationRunningStateChanged(true);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
@@ -340,31 +312,6 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
}
@Test
- public void testAnimatingDenylist() {
- final WindowState window = createWindow("overrideWindow");
- window.mAttrs.packageName = "com.android.test";
- parcelLayoutParams(window);
- when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
- assertEquals(0, mPolicy.getPreferredModeId(window));
- assertTrue(mPolicy.updateFrameRateVote(window));
- assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- window.mActivityRecord.mSurfaceAnimator.startAnimation(
- window.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(window));
- assertTrue(mPolicy.updateFrameRateVote(window));
- assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
- }
-
- @Test
public void testAnimatingCamera() {
final WindowState cameraUsingWindow = createWindow("cameraUsingWindow");
cameraUsingWindow.mAttrs.packageName = "com.android.test";
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index ae0c6e551246..7e8bd38fb6a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -285,6 +285,52 @@ public class RootWindowContainerTests extends WindowTestsBase {
}
@Test
+ public void testTaskLayerRankFreeform() {
+ mSetFlagsRule.enableFlags(com.android.window.flags.Flags
+ .FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE);
+ final Task[] freeformTasks = new Task[3];
+ final WindowProcessController[] processes = new WindowProcessController[3];
+ for (int i = 0; i < freeformTasks.length; i++) {
+ freeformTasks[i] = new TaskBuilder(mSupervisor)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).setCreateActivity(true).build();
+ final ActivityRecord r = freeformTasks[i].getTopMostActivity();
+ r.setState(RESUMED, "test");
+ processes[i] = r.app;
+ }
+ resizeDisplay(mDisplayContent, 2400, 2000);
+ // ---------
+ // | 2 | 1 |
+ // ---------
+ // | 0 | |
+ // ---------
+ freeformTasks[2].setBounds(0, 0, 1000, 1000);
+ freeformTasks[1].setBounds(1000, 0, 2000, 1000);
+ freeformTasks[0].setBounds(0, 1000, 1000, 2000);
+ mRootWindowContainer.rankTaskLayers();
+ assertEquals(1, freeformTasks[2].mLayerRank);
+ assertEquals(2, freeformTasks[1].mLayerRank);
+ assertEquals(3, freeformTasks[0].mLayerRank);
+ assertFalse("Top doesn't need perceptible hint", (processes[2].getActivityStateFlags()
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+ assertTrue((processes[1].getActivityStateFlags()
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+ assertTrue((processes[0].getActivityStateFlags()
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+
+ // Make task2 occlude half of task0.
+ clearInvocations(mAtm);
+ freeformTasks[2].setBounds(0, 0, 1000, 1500);
+ waitHandlerIdle(mWm.mH);
+ // The process of task0 will demote from perceptible to visible.
+ final int stateFlags0 = processes[0].getActivityStateFlags();
+ assertTrue((stateFlags0
+ & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0);
+ assertFalse((stateFlags0
+ & WindowProcessController.ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM) != 0);
+ verify(mAtm).updateOomAdj();
+ }
+
+ @Test
public void testForceStopPackage() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostActivity();
@@ -331,6 +377,8 @@ public class RootWindowContainerTests extends WindowTestsBase {
final WindowProcessController proc = mSystemServicesTestRule.addProcess(
activity.packageName, activity.processName,
6789 /* pid */, activity.info.applicationInfo.uid);
+ mAtm.mInternal.preBindApplication(proc, proc.mInfo);
+ assertTrue(proc.registeredForActivityConfigChanges());
assertFalse(proc.mHasEverAttached);
try {
mRootWindowContainer.attachApplication(proc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 55a7089f3344..a92fe3afbd78 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.mock;
import android.app.ActivityOptions;
import android.content.pm.ActivityInfo;
+import android.os.Binder;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.view.RemoteAnimationAdapter;
@@ -61,7 +62,8 @@ public class SafeActivityOptionsTest {
opts1.setLaunchDisplayId(5);
final ActivityOptions opts2 = ActivityOptions.makeBasic();
opts2.setLaunchDisplayId(6);
- final SafeActivityOptions options = new SafeActivityOptions(opts1);
+ final SafeActivityOptions options = new SafeActivityOptions(opts1,
+ Binder.getCallingPid(), Binder.getCallingUid());
final ActivityOptions result = options.mergeActivityOptions(opts1, opts2);
assertEquals(6, result.getLaunchDisplayId());
}
@@ -75,7 +77,8 @@ public class SafeActivityOptionsTest {
final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic()
.setLaunchTaskDisplayArea(token)
.setLaunchDisplayId(launchDisplayId)
- .setCallerDisplayId(callerDisplayId))
+ .setCallerDisplayId(callerDisplayId),
+ Binder.getCallingPid(), Binder.getCallingUid())
.selectiveCloneLaunchOptions();
assertSame(clone.getOriginalOptions().getLaunchTaskDisplayArea(), token);
@@ -86,8 +89,9 @@ public class SafeActivityOptionsTest {
@Test
public void test_selectiveCloneLunchRootTask() {
final WindowContainerToken token = mock(WindowContainerToken.class);
- final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic()
- .setLaunchRootTask(token))
+ final SafeActivityOptions clone = new SafeActivityOptions(
+ ActivityOptions.makeBasic().setLaunchRootTask(token),
+ Binder.getCallingPid(), Binder.getCallingUid())
.selectiveCloneLaunchOptions();
assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
@@ -97,7 +101,8 @@ public class SafeActivityOptionsTest {
public void test_selectiveCloneLunchRemoteTransition() {
final RemoteTransition transition = mock(RemoteTransition.class);
final SafeActivityOptions clone = new SafeActivityOptions(
- ActivityOptions.makeRemoteTransition(transition))
+ ActivityOptions.makeRemoteTransition(transition),
+ Binder.getCallingPid(), Binder.getCallingUid())
.selectiveCloneLaunchOptions();
assertSame(clone.getOriginalOptions().getRemoteTransition(), transition);
@@ -157,6 +162,10 @@ public class SafeActivityOptionsTest {
verifySecureExceptionThrown(activityOptions, taskSupervisor);
activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setTaskAlwaysOnTop(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
verifySecureExceptionThrown(activityOptions, taskSupervisor);
@@ -202,7 +211,8 @@ public class SafeActivityOptionsTest {
private void verifySecureExceptionThrown(ActivityOptions activityOptions,
ActivityTaskSupervisor taskSupervisor, TaskDisplayArea mockTda) {
- SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions);
+ SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions,
+ Binder.getCallingPid(), Binder.getCallingUid());
if (mockTda != null) {
spyOn(safeActivityOptions);
doReturn(mockTda).when(safeActivityOptions).getLaunchTaskDisplayArea(any(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7bce8285972c..bf96f0eb03b8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -63,9 +63,9 @@ import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -87,6 +87,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -95,13 +96,11 @@ import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -183,15 +182,32 @@ public class SizeCompatTests extends WindowTestsBase {
DeviceConfig.setProperties(mInitialConstrainDisplayApisFlags);
}
- private void setUpApp(DisplayContent display) {
- mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
- mActivity = mTask.getTopNonFinishingActivity();
+ private ActivityRecord setUpApp(DisplayContent display) {
+ return setUpApp(display, null /* appBuilder */);
+ }
+
+ private ActivityRecord setUpApp(DisplayContent display, ActivityBuilder appBuilder) {
+ // Use the real package name (com.android.frameworks.wmtests) so that
+ // EnableCompatChanges/DisableCompatChanges can take effect.
+ // Otherwise the fake WindowTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME will make
+ // PlatformCompat#isChangeEnabledByPackageName always return default value.
+ final ComponentName componentName = ComponentName.createRelative(
+ mContext, SizeCompatTests.class.getName());
+ mTask = new TaskBuilder(mSupervisor).setDisplay(display).setComponent(componentName)
+ .build();
+ final ActivityBuilder builder = appBuilder != null ? appBuilder : new ActivityBuilder(mAtm);
+ mActivity = builder.setTask(mTask).setComponent(componentName).build();
doReturn(false).when(mActivity).isImmersiveMode(any());
+ return mActivity;
+ }
+
+ private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh) {
+ return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */);
}
- private void setUpDisplaySizeWithApp(int dw, int dh) {
+ private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, ActivityBuilder appBuilder) {
final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mAtm, dw, dh);
- setUpApp(builder.build());
+ return setUpApp(builder.build(), appBuilder);
}
@Test
@@ -352,15 +368,13 @@ public class SizeCompatTests extends WindowTestsBase {
.setCanRotate(false)
.setNotch(cutoutHeight)
.build();
- setUpApp(display);
display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
- final ActivityRecord activity = getActivityBuilderOnSameTask()
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
- .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE)
- .build();
+ .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));
doReturn(true).when(activity).isImmersiveMode(any());
addWindowToActivity(activity);
@@ -424,17 +438,16 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
@DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() {
- final int notchHeight = 100;
- setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build());
-
- final Rect displayBounds = mActivity.mDisplayContent.getWindowConfiguration().getBounds();
final float aspectRatio = 1.2f;
- final ActivityRecord activity = getActivityBuilderOnSameTask()
+ final ActivityBuilder activityBuilder = new ActivityBuilder(mAtm)
.setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)
.setMinAspectRatio(aspectRatio)
.setMaxAspectRatio(aspectRatio)
- .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
- .build();
+ .setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+ final int notchHeight = 100;
+ final ActivityRecord activity = setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800)
+ .setNotch(notchHeight).build(), activityBuilder);
+ final Rect displayBounds = activity.mDisplayContent.getWindowConfiguration().getBounds();
final Rect appBounds = activity.getWindowConfiguration().getAppBounds();
// The parent configuration doesn't change since the first resolved configuration, so the
@@ -1327,12 +1340,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideMinAspectRatioSmall_overridden() {
final int dh = 1200;
final int dw = 1000;
- setUpDisplaySizeWithApp(dw, dh);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(dw, dh, new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
final Rect bounds = activity.getBounds();
assertEquals(dh, bounds.height());
@@ -1346,13 +1355,9 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideMinAspectRatioSmall_notOverridden() {
final int dh = 1200;
final int dw = 1000;
- setUpDisplaySizeWithApp(dw, dh);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(dw, dh, new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));
// Activity's requested aspect ratio is larger than OVERRIDE_MIN_ASPECT_RATIO_SMALL,
// so OVERRIDE_MIN_ASPECT_RATIO_SMALL is ignored.
@@ -1366,12 +1371,8 @@ public class SizeCompatTests extends WindowTestsBase {
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioMedium() {
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1388,13 +1389,8 @@ public class SizeCompatTests extends WindowTestsBase {
final int notchHeight = 200;
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, dh)
.setNotch(notchHeight).setSystemDecorations(true).build();
- mTask = new TaskBuilder(mSupervisor).setDisplay(display).build();
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .setMinAspectRatio(2f)
- .build();
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).setMinAspectRatio(2f));
// The per-package override should have no effect, because the manifest aspect ratio is
// larger (2:1)
@@ -1411,13 +1407,9 @@ public class SizeCompatTests extends WindowTestsBase {
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioLargerThanManifest() {
- setUpDisplaySizeWithApp(1400, 1600);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .setMinAspectRatio(1.1f)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1400, 1600,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(1.1f));
// The per-package override should have no effect, because the manifest aspect ratio is
// larger (2:1)
@@ -1430,12 +1422,8 @@ public class SizeCompatTests extends WindowTestsBase {
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioLarge() {
- setUpDisplaySizeWithApp(1500, 1600);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1500, 1600,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
// The per-package override forces the activity into a 16:9 aspect ratio
assertEquals(1600, activity.getBounds().height());
@@ -1449,13 +1437,8 @@ public class SizeCompatTests extends WindowTestsBase {
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatio_Both() {
// If multiple override aspect ratios are set, we should use the largest one
-
- setUpDisplaySizeWithApp(1400, 1600);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1400, 1600,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
// The per-package override forces the activity into a 16:9 aspect ratio
assertEquals(1600, activity.getBounds().height());
@@ -1470,11 +1453,7 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() {
// In this test, the activity's orientation isn't fixed to portrait, therefore the override
// isn't applied.
-
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200);
// The per-package override should have no effect
assertEquals(1200, activity.getBounds().height());
@@ -1497,13 +1476,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() {
// In this test, the activity's orientation isn't fixed to portrait, therefore the override
// isn't applied.
-
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE));
// The per-package override should have no effect
assertEquals(1200, activity.getBounds().height());
@@ -1524,12 +1498,8 @@ public class SizeCompatTests extends WindowTestsBase {
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() {
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1550,10 +1520,7 @@ public class SizeCompatTests extends WindowTestsBase {
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
@DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() {
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200);
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1568,13 +1535,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() {
// In this test, the activity's orientation isn't fixed to portrait, therefore the override
// isn't applied.
-
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE));
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
@@ -1588,12 +1550,8 @@ public class SizeCompatTests extends WindowTestsBase {
// In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
// OVERRIDE_MIN_ASPECT_RATIO being also set.
- setUpDisplaySizeWithApp(1000, 1200);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE));
// The per-package override should have no effect
assertEquals(1200, activity.getBounds().height());
@@ -1604,18 +1562,14 @@ public class SizeCompatTests extends WindowTestsBase {
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioLargeForResizableAppInSplitScreen() {
- setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800,
+ new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, activity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, 1000, 1400);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
@@ -2002,7 +1956,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to multi-window which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -2038,7 +1992,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to multi-window which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -2171,7 +2125,7 @@ public class SizeCompatTests extends WindowTestsBase {
setUpDisplaySizeWithApp(screenWidth, screenHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mActivity.mWmService.mAppCompatConfiguration
- .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
+ .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
@@ -2192,7 +2146,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -2437,13 +2391,11 @@ public class SizeCompatTests extends WindowTestsBase {
spyOn(activity.mWmService.mAppCompatConfiguration);
doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
.isUserAppAspectRatioSettingsEnabled();
- // Set user aspect ratio override
- final IPackageManager pm = mAtm.getPackageManager();
- try {
- doReturn(aspectRatio).when(pm)
- .getUserMinAspectRatio(activity.packageName, activity.mUserId);
- } catch (RemoteException ignored) {
- }
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ // Set user aspect ratio override.
+ doReturn(aspectRatio).when(aspectRatioOverrides).getUserMinAspectRatioOverrideCode();
prepareLimitedBounds(activity, screenOrientation, isUnresizable);
@@ -2462,10 +2414,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
final int displayWidth = 1400;
final int displayHeight = 1600;
- setUpDisplaySizeWithApp(displayWidth, displayHeight);
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setMinAspectRatio(1.1f)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
+ new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
// Setup Letterbox Configuration
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
@@ -2483,10 +2433,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideSplitScreenAspectRatioForUnresizablePortraitAppsFromLandscape() {
final int displayWidth = 1600;
final int displayHeight = 1400;
- setUpDisplaySizeWithApp(displayWidth, displayHeight);
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setMinAspectRatio(1.1f)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
+ new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
// Setup Letterbox Configuration
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
@@ -2505,10 +2453,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeApps() {
final int displayWidth = 1400;
final int displayHeight = 1600;
- setUpDisplaySizeWithApp(displayWidth, displayHeight);
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setMinAspectRatio(1.1f)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
+ new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
// Setup Letterbox Configuration
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
@@ -2527,10 +2473,8 @@ public class SizeCompatTests extends WindowTestsBase {
public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeAppsFromLandscape() {
final int displayWidth = 1600;
final int displayHeight = 1400;
- setUpDisplaySizeWithApp(displayWidth, displayHeight);
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setMinAspectRatio(1.1f)
- .build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
+ new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
// Setup Letterbox Configuration
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
@@ -2549,8 +2493,7 @@ public class SizeCompatTests extends WindowTestsBase {
mAtm.mDevEnableNonResizableMultiWindow = true;
final int screenWidth = 1800;
final int screenHeight = 1000;
- setUpDisplaySizeWithApp(screenWidth, screenHeight);
- final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(screenWidth, screenHeight);
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate real display with top insets.
@@ -2561,7 +2504,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, activity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
@@ -2585,8 +2528,7 @@ public class SizeCompatTests extends WindowTestsBase {
mAtm.mDevEnableNonResizableMultiWindow = true;
final int screenWidth = 1000;
final int screenHeight = 1800;
- setUpDisplaySizeWithApp(screenWidth, screenHeight);
- final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+ final ActivityRecord activity = setUpDisplaySizeWithApp(screenWidth, screenHeight);
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate real display with top insets.
@@ -2597,7 +2539,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, activity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
@@ -2616,18 +2558,18 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN})
public void testOverrideMinAspectRatioExcludePortraitFullscreen() {
- setUpDisplaySizeWithApp(2600, 1600);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);
-
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+ resizeDisplay(mDisplayContent, 2600, 1600);
+ mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);
// Non-resizable portrait activity
- prepareUnresizable(activity, 0f, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ final ActivityRecord activity = setUpApp(mDisplayContent, new ActivityBuilder(mAtm)
+ .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
// At first, OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply, because the
// display is in landscape
@@ -2645,16 +2587,16 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN})
public void testOverrideMinAspectRatioExcludePortraitFullscreenNotApplied() {
// In this test, the activity is not in fullscreen, so the override is not applied
- setUpDisplaySizeWithApp(2600, 1600);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);
+ resizeDisplay(mDisplayContent, 2600, 1600);
+ mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);
- // Create a size compat activity on the same task.
- final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+ final ActivityRecord activity = setUpApp(mDisplayContent);
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, activity.getDisplayContent());
@@ -2718,7 +2660,7 @@ public class SizeCompatTests extends WindowTestsBase {
setUpDisplaySizeWithApp(screenWidth, screenHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mActivity.mWmService.mAppCompatConfiguration
- .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
+ .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
@@ -2739,7 +2681,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -2767,7 +2709,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, displayWidth, getExpectedSplitSize(displayHeight));
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -2803,7 +2745,7 @@ public class SizeCompatTests extends WindowTestsBase {
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(displayWidth), displayHeight);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -2934,7 +2876,7 @@ public class SizeCompatTests extends WindowTestsBase {
// App bounds should be 700x1400 with the ratio as the display.
assertEquals(rotatedDisplayBounds.height(), rotatedActivityBounds.height());
assertEquals(rotatedDisplayBounds.height() * rotatedDisplayBounds.height()
- / rotatedDisplayBounds.width(), rotatedActivityBounds.width());
+ / rotatedDisplayBounds.width(), rotatedActivityBounds.width());
}
@Test
@@ -2954,7 +2896,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Launch another portrait fixed app.
spyOn(mTask);
setBooted(display.mWmService.mAtmService);
- final ActivityRecord newActivity = new ActivityBuilder(display.mWmService.mAtmService)
+ final ActivityRecord newActivity = getActivityBuilderWithoutTask()
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.setTask(mTask)
@@ -2983,6 +2925,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
public void testDisplayIgnoreOrientationRequest_orientationChangedToUnspecified() {
// Set up a display in landscape and ignoring orientation request.
setUpDisplaySizeWithApp(2800, 1400);
@@ -3021,7 +2964,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Launch another portrait fixed app with max aspect ratio as 1.3.
spyOn(mTask);
setBooted(display.mWmService.mAtmService);
- final ActivityRecord newActivity = new ActivityBuilder(display.mWmService.mAtmService)
+ final ActivityRecord newActivity = getActivityBuilderWithoutTask()
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setMaxAspectRatio(1.3f)
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
@@ -3382,7 +3325,7 @@ public class SizeCompatTests extends WindowTestsBase {
final Rect originalBounds = new Rect(mActivity.getBounds());
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, 1000, 1400);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -3437,6 +3380,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
public void testSupportsNonResizableInSplitScreen_letterboxForAspectRatioRestriction() {
// Support non resizable in multi window
mAtm.mDevEnableNonResizableMultiWindow = true;
@@ -3497,7 +3441,7 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, 1400, 1000);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -3521,7 +3465,7 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);
// Move activity to split screen which takes half of the screen.
- mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
organizer.mPrimary.setBounds(0, 0, 1000, 1400);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
@@ -3962,7 +3906,7 @@ public class SizeCompatTests extends WindowTestsBase {
final DisplayPolicy policy = display.getDisplayPolicy();
DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
display.mBaseDisplayHeight, display.mBaseDisplayWidth);
- decorInfo.mNonDecorInsets.set(130, 0, 60, 0);
+ decorInfo.mNonDecorInsets.set(130, 0, 60, 0);
spyOn(policy);
doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
display.mBaseDisplayHeight, display.mBaseDisplayWidth);
@@ -4050,9 +3994,9 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
public void testPortraitCloseToSquareDisplayWithTaskbar_insetsOverridden_notLetterboxed() {
+ final DisplayContent display = mDisplayContent;
// Set up portrait close to square display.
- setUpDisplaySizeWithApp(2200, 2280);
- final DisplayContent display = mActivity.mDisplayContent;
+ resizeDisplay(display, 2200, 2280);
display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate insets, final app bounds are (0, 0, 2200, 2130) - landscape.
final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
@@ -4063,12 +4007,12 @@ public class SizeCompatTests extends WindowTestsBase {
.setInsetsSize(Insets.of(0, 0, 0, 150))
};
display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
- assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo());
- display.sendNewConfiguration();
+ if (display.getDisplayPolicy().updateDecorInsetsInfo()) {
+ display.sendNewConfiguration();
+ }
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
// Activity should not be letterboxed and should have portrait app bounds even though
// orientation is not respected with insets as insets have been decoupled.
@@ -4084,9 +4028,9 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
@DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() {
+ final DisplayContent display = mDisplayContent;
// Set up portrait close to square display
- setUpDisplaySizeWithApp(2200, 2280);
- final DisplayContent display = mActivity.mDisplayContent;
+ resizeDisplay(display, 2200, 2280);
display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
@@ -4097,12 +4041,12 @@ public class SizeCompatTests extends WindowTestsBase {
.setInsetsSize(Insets.of(0, 0, 0, 150))
};
display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
- assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo());
- display.sendNewConfiguration();
+ if (display.getDisplayPolicy().updateDecorInsetsInfo()) {
+ display.sendNewConfiguration();
+ }
- final ActivityRecord activity = getActivityBuilderOnSameTask()
- .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
- .build();
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));
final Rect bounds = activity.getBounds();
// Activity should be letterboxed and should have portrait app bounds
@@ -4114,9 +4058,9 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
@DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
- setUpDisplaySizeWithApp(2200, 2280);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- final DisplayContent dc = mActivity.mDisplayContent;
+ final DisplayContent dc = mDisplayContent;
+ resizeDisplay(dc, 2200, 2280);
+ dc.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
"navbar");
@@ -4126,14 +4070,14 @@ public class SizeCompatTests extends WindowTestsBase {
.setInsetsSize(Insets.of(0, 0, 0, 150))
};
dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
- assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
- dc.sendNewConfiguration();
+ if (dc.getDisplayPolicy().updateDecorInsetsInfo()) {
+ dc.sendNewConfiguration();
+ }
- final ActivityRecord activity = getActivityBuilderOnSameTask()
+ final ActivityRecord activity = setUpApp(dc, new ActivityBuilder(mAtm)
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
- .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE)
- .build();
+ .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));
// To force config to update again but with the same landscape orientation.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
@@ -4171,13 +4115,10 @@ public class SizeCompatTests extends WindowTestsBase {
.setCutout(0, 100, 0, 150)
.build();
- setUpApp(display);
-
- final ActivityRecord activity = getActivityBuilderOnSameTask()
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setMaxAspectRatio(2.1f)
- .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)
- .build();
+ .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
// The activity height is 2100 and the display's app bounds height is 2050, so the activity
// cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
@@ -4193,17 +4134,15 @@ public class SizeCompatTests extends WindowTestsBase {
.setCutout(100, 0, 150, 0)
.build();
- setUpApp(display);
-
- final ActivityRecord activity = getActivityBuilderOnSameTask()
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setMaxAspectRatio(2.1f)
- .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)
- .build();
+ .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
// The activity width is 2100 and the display's app bounds width is 2250, so the activity
// can be aligned inside parentAppBounds
assertEquals(activity.getBounds(), new Rect(175, 0, 2275, 1000));
}
+
@Test
@DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() {
@@ -4213,12 +4152,10 @@ public class SizeCompatTests extends WindowTestsBase {
.setCutout(100, 0, 150, 0)
.build();
- setUpApp(display);
- final ActivityRecord activity = getActivityBuilderOnSameTask()
+ final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setMaxAspectRatio(2.1f)
- .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)
- .build();
+ .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
// The activity width is 2100 and the display's app bounds width is 2050, so the activity
// cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
assertEquals(activity.getBounds(), display.getBounds());
@@ -4259,7 +4196,7 @@ public class SizeCompatTests extends WindowTestsBase {
final DisplayPolicy policy = display.getDisplayPolicy();
DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
display.mBaseDisplayHeight, display.mBaseDisplayWidth);
- decorInfo.mNonDecorInsets.set(0, 130, 0, 60);
+ decorInfo.mNonDecorInsets.set(0, 130, 0, 60);
spyOn(policy);
doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
display.mBaseDisplayHeight, display.mBaseDisplayWidth);
@@ -4512,7 +4449,7 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
// Set task as freeform
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds());
Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
@@ -4846,6 +4783,198 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatioAppliedForFixedOrientationCameraActivities() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 4.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // Create fixed portrait activity.
+ final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+ final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatioForFixedOrientationCameraActivitiesPortraitWindow() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create portrait display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 1600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 4.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // Create fixed portrait activity.
+ final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+ final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatioAppliedInsteadOfDefaultAspectRatio() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 5.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // App's target min aspect ratio - this should not be used, as camera controls aspect ratio.
+ final float targetMinAspectRatio = 4.0f;
+
+ // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
+ final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(cameraCompatAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatio_defaultAspectRatioAppliedWhenGreater() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 5.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // App's target min aspect ratio bigger than camera compat aspect ratio - use that instead.
+ final float targetMinAspectRatio = 6.0f;
+
+ // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
+ final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(targetMinAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ public void testUniversalResizeable() {
+ mWm.mConstants.mIgnoreActivityOrientationRequestSmallScreen = true;
+ mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
+ setUpApp(mDisplayContent);
+ final float maxAspect = 1.8f;
+ final float minAspect = 1.5f;
+ prepareLimitedBounds(mActivity, maxAspect, minAspect,
+ ActivityInfo.SCREEN_ORIENTATION_NOSENSOR, true /* isUnresizable */);
+
+ assertTrue(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
+ mWm, true /* isLargeScreen */, false /* forActivity */));
+ assertTrue(mActivity.isUniversalResizeable());
+ assertTrue(mActivity.isResizeable());
+ assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+ assertEquals(mActivity.getTask().getBounds(), mActivity.getBounds());
+ final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
+ .getAppCompatAspectRatioPolicy();
+ assertEquals(0, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+ assertEquals(0, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+ // Compat override can still take effect.
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
+ assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+ // User override can still take effect.
+ doReturn(USER_MIN_ASPECT_RATIO_3_2).when(aspectRatioOverrides)
+ .getUserMinAspectRatioOverrideCode();
+ assertFalse(mActivity.isResizeable());
+ assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+ assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+
+ // Activity can opt-out the resizability by component level property.
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ final PackageManager.Property property = new PackageManager.Property("propertyName",
+ true /* value */, name.getPackageName(), name.getClassName());
+ // Activity level.
+ try {
+ doReturn(property).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ name.getPackageName(), name.getClassName(), 0 /* userId */);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ assertFalse(optOutActivity.isUniversalResizeable());
+
+ // Application level.
+ try {
+ doReturn(property).when(pm).getProperty(
+ WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ name.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
+ .setComponent(getUniqueComponentName(mContext.getPackageName()))
+ .setTask(mTask).build();
+ assertFalse(optOutAppActivity.isUniversalResizeable());
+ assertFalse(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
+ mWm, true /* isLargeScreen */, false /* forActivity */));
+ }
+
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT})
+ public void testUniversalResizeableByDefault() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ setUpApp(mDisplayContent);
+ assertFalse(mActivity.isUniversalResizeable());
+
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ makeDisplayLargeScreen(mDisplayContent);
+ assertTrue(mActivity.isUniversalResizeable());
+ }
+
+ @Test
public void testClearSizeCompat_resetOverrideConfig() {
final int origDensity = 480;
final int newDensity = 520;
@@ -4868,6 +4997,25 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
+ /**
+ * {@code canEnterDesktopMode} is called when {@link CameraCompatFreeformPolicy} is created in
+ * {@link AppCompatCameraPolicy}.
+ *
+ * <p>{@link #allowDesktopMode()} needs to be called before {@link DisplayContent} is created.
+ */
+ private void allowDesktopMode() {
+ doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
+ }
+
+ private void setupCameraCompatAspectRatio(float cameraCompatAspectRatio,
+ @NonNull DisplayContent display) {
+ CameraCompatFreeformPolicy cameraPolicy = display.mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy;
+ spyOn(cameraPolicy);
+ doReturn(true).when(cameraPolicy).shouldCameraCompatControlAspectRatio(any());
+ doReturn(cameraCompatAspectRatio).when(cameraPolicy).getCameraCompatAspectRatio(any());
+ }
+
private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
final AppCompatReachabilityOverrides reachabilityOverrides =
mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
@@ -4977,17 +5125,13 @@ public class SizeCompatTests extends WindowTestsBase {
*/
private ActivityRecord buildActivityRecord(boolean supportsSizeChanges, int resizeMode,
@ScreenOrientation int screenOrientation) {
- return getActivityBuilderOnSameTask()
+ return getActivityBuilderWithoutTask().setTask(mTask)
.setResizeMode(resizeMode)
.setSupportsSizeChanges(supportsSizeChanges)
.setScreenOrientation(screenOrientation)
.build();
}
- private ActivityBuilder getActivityBuilderOnSameTask() {
- return getActivityBuilderWithoutTask().setTask(mTask);
- }
-
private ActivityBuilder getActivityBuilderWithoutTask() {
return new ActivityBuilder(mAtm)
.setComponent(ComponentName.createRelative(mContext,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 9967ccebeb1f..7dba1422d61d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -21,7 +21,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static org.junit.Assert.assertEquals;
@@ -165,31 +164,6 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
}
@Test
- public void testDelayingAnimationStart() {
- mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
- mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- verifyZeroInteractions(mSpec);
- assertAnimating(mAnimatable);
- assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed());
- mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
- verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), any());
- }
-
- @Test
- public void testDelayingAnimationStartAndCancelled() {
- mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
- mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- mAnimatable.mSurfaceAnimator.cancelAnimation();
- verifyZeroInteractions(mSpec);
- assertNotAnimating(mAnimatable);
- assertTrue(mAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
- verify(mTransaction).remove(eq(mAnimatable.mLeash));
- }
-
- @Test
public void testTransferAnimation() {
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
ANIMATION_TYPE_APP_TRANSITION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 08622e68629a..921228ff2a5b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -78,6 +78,8 @@ import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.WmProtoLogGroups;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -183,6 +185,8 @@ public class SystemServicesTestRule implements TestRule {
}
private void setUp() {
+ ProtoLog.init(WmProtoLogGroups.values());
+
if (mOnBeforeServicesCreated != null) {
mOnBeforeServicesCreated.run();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index b5953839ca8f..f795d93b2487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -605,7 +605,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
@Test
public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
DisplayContent display = createNewDisplay();
- doReturn(true).when(display).supportsSystemDecorations();
+ doReturn(true).when(display).isSystemDecorationsSupported();
// Remove the current home root task if it exists so a new one can be created below.
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
@@ -622,7 +622,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
DisplayContent display = createNewDisplay();
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(false).when(display).supportsSystemDecorations();
+ doReturn(false).when(display).isSystemDecorationsSupported();
assertNull(taskDisplayArea.getRootHomeTask());
assertNull(taskDisplayArea.getOrCreateRootHomeTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 3c921c612705..4568c77204a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,7 +249,7 @@ public class TaskLaunchParamsModifierTests extends
ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
ActivityRecord source = createSourceActivity(freeformDisplay);
source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId;
- source.noDisplay = true;
+ source.setIsNoDisplay(true);
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder()
@@ -272,7 +272,7 @@ public class TaskLaunchParamsModifierTests extends
ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
ActivityRecord source = createSourceActivity(freeformDisplay);
source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
- source.noDisplay = true;
+ source.setIsNoDisplay(true);
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 4b03483d43b9..e4512c31069a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -705,50 +704,6 @@ public class TaskTests extends WindowTestsBase {
}
@Test
- public void testTopActivityEligibleForUserAspectRatioButton() {
- DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
- final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- final Task task = rootTask.getBottomMostTask();
- final ActivityRecord root = task.getTopNonFinishingActivity();
- spyOn(mWm.mAppCompatConfiguration);
- spyOn(root);
- spyOn(root.mAppCompatController.getAppCompatAspectRatioOverrides());
-
- doReturn(true).when(root).fillsParent();
- doReturn(true).when(
- root.mAppCompatController.getAppCompatAspectRatioOverrides())
- .shouldEnableUserAspectRatioSettings();
- doReturn(false).when(root).inSizeCompatMode();
- doReturn(task).when(root).getOrganizedTask();
-
- // The button should be eligible to be displayed
- assertTrue(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
-
- // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
- doReturn(false).when(
- root.mAppCompatController.getAppCompatAspectRatioOverrides())
- .shouldEnableUserAspectRatioSettings();
- assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
- doReturn(true).when(root.mAppCompatController
- .getAppCompatAspectRatioOverrides()).shouldEnableUserAspectRatioSettings();
-
- // When in size compat mode the button is not enabled
- doReturn(true).when(root).inSizeCompatMode();
- assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
- doReturn(false).when(root).inSizeCompatMode();
-
- // When the top activity is transparent, the button is not enabled
- doReturn(false).when(root).fillsParent();
- assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
- doReturn(true).when(root).fillsParent();
- }
-
- @Test
public void testIsTopActivityTranslucent() {
DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
@@ -2112,17 +2067,6 @@ public class TaskTests extends WindowTestsBase {
}
@Test
- public void getTaskInfoPropagatesCameraCompatMode() {
- final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- final ActivityRecord activity = task.getTopMostActivity();
- activity.mAppCompatController.getAppCompatCameraOverrides().setFreeformCameraCompatMode(
- CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
-
- assertEquals(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE,
- task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode);
- }
-
- @Test
public void testUpdateTaskDescriptionOnReparent() {
final Task rootTask1 = createTask(mDisplayContent);
final Task rootTask2 = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 00ecd008cde7..ccce57a81e41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -19,11 +19,9 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.Surface.ROTATION_0;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -206,13 +204,13 @@ class TestDisplayContent extends DisplayContent {
final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
spyOn(displayPolicy);
if (mSystemDecorations) {
- doReturn(true).when(newDisplay).supportsSystemDecorations();
+ doReturn(true).when(newDisplay).isSystemDecorationsSupported();
doReturn(true).when(displayPolicy).hasNavigationBar();
- doReturn(NAV_BAR_BOTTOM).when(displayPolicy).navigationBarPosition(anyInt());
+ doReturn(true).when(displayPolicy).hasBottomNavigationBar();
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
- doReturn(false).when(newDisplay).supportsSystemDecorations();
+ doReturn(false).when(newDisplay).isSystemDecorationsSupported();
}
// Update the display policy to make the screen fully turned on so animation is allowed
displayPolicy.screenTurningOn(null /* screenOnListener */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index eebb487d16cd..9e9874b32893 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -103,8 +103,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
- return null;
+ public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
+ return false;
}
@Override
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 064b434e1a6b..039a3ddd3e4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -34,7 +34,6 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -52,7 +51,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1633,6 +1631,8 @@ public class TransitionTests extends WindowTestsBase {
transition.collect(taskA);
transition.setTransientLaunch(recent, taskA);
taskRecent.moveToFront("move-recent-to-front");
+ recent.setVisibility(true);
+ recent.setState(ActivityRecord.State.RESUMED, "test");
// During collecting and playing, the recent is on top so it is visible naturally.
// While B needs isTransientVisible to keep visibility because it is occluded by recents.
@@ -1645,15 +1645,21 @@ public class TransitionTests extends WindowTestsBase {
// Switch to another task. For example, use gesture navigation to switch tasks.
taskB.moveToFront("move-b-to-front");
+ appB.setVisibility(true);
// The previous app (taskA) should be paused first so it loses transient visible. Because
// visually it is taskA -> taskB, the pause -> resume order should be the same.
assertFalse(controller.isTransientVisible(taskA));
- // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
- // to avoid the latency to resume the current top, i.e. appB.
- assertTrue(controller.isTransientVisible(taskRecent));
- // The recent is paused after the transient transition is finished.
- controller.finishTransition(ActionChain.testFinish(transition));
+ // The recent is occluded by appB.
assertFalse(controller.isTransientVisible(taskRecent));
+ // Active transient launch won't be paused if the transition is not finished. It is to
+ // avoid the latency to resume the current top (appB) by waiting for both recent and appA
+ // to complete pause.
+ assertEquals(recent, taskRecent.getResumedActivity());
+ assertFalse(taskRecent.startPausing(false /* uiSleeping */, appB /* resuming */, "test"));
+ // ActivityRecord#makeInvisible will add the invisible recent to the stopping list.
+ // So when the transition finished, the recent can still be notified to pause and stop.
+ mDisplayContent.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
+ assertTrue(mSupervisor.mStoppingActivities.contains(recent));
}
@Test
@@ -2355,6 +2361,11 @@ public class TransitionTests extends WindowTestsBase {
// ChangeInfo#mCommonAncestor should be set after reparent.
final Transition.ChangeInfo change = transition.mChanges.get(activity);
assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
+
+ // WindowContainer#onDisplayChanged should collect the moved task.
+ final DisplayContent newDisplay = createNewDisplay();
+ newParent.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+ assertTrue(transition.mParticipants.contains(newParent));
}
@Test
@@ -2879,17 +2890,14 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testTransitionsTriggerPerformanceHints() {
- final boolean explicitRefreshRateHints = explicitRefreshRateHints();
final var session = new SystemPerformanceHinter.HighPerfSession[1];
- if (explicitRefreshRateHints) {
- final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
- spyOn(perfHinter);
- doAnswer(invocation -> {
- session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
- spyOn(session[0]);
- return session[0];
- }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
- }
+ final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
+ spyOn(perfHinter);
+ doAnswer(invocation -> {
+ session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
+ spyOn(session[0]);
+ return session[0];
+ }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
final TransitionController controller = mDisplayContent.mTransitionController;
final TestTransitionPlayer player = registerTestTransitionPlayer();
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2901,15 +2909,11 @@ public class TransitionTests extends WindowTestsBase {
player.start();
verify(mDisplayContent).enableHighPerfTransition(true);
- if (explicitRefreshRateHints) {
- verify(session[0]).start();
- }
+ verify(session[0]).start();
player.finish();
verify(mDisplayContent).enableHighPerfTransition(false);
- if (explicitRefreshRateHints) {
- verify(session[0]).close();
- }
+ verify(session[0]).close();
}
@Test
@@ -2934,10 +2938,12 @@ public class TransitionTests extends WindowTestsBase {
controller.requestStartTransition(transit, task, null, null);
player.start();
+ // always include config-at-end activity since it is considered "independent" due to
+ // changing at a different time.
+ assertTrue(player.mLastReady.getChanges().stream()
+ .anyMatch((change -> change.getActivityComponent() != null
+ && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0)));
assertTrue(activity.isConfigurationDispatchPaused());
- // config-at-end flag must propagate up to task if activity was promoted.
- assertTrue(player.mLastReady.getChange(
- task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END));
player.finish();
assertFalse(activity.isConfigurationDispatchPaused());
}
@@ -2966,11 +2972,14 @@ public class TransitionTests extends WindowTestsBase {
controller.requestStartTransition(transit, task, null, null);
player.start();
- // config-at-end flag must propagate up to task even when reparented (since config-at-end
- // only cares about after-end state).
- assertTrue(player.mLastReady.getChange(
- task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END));
+ // always include config-at-end activity since it is considered "independent" due to
+ // changing at a different time.
+ assertTrue(player.mLastReady.getChanges().stream()
+ .anyMatch((change -> change.getActivityComponent() != null
+ && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0)));
+ assertTrue(activity.isConfigurationDispatchPaused());
player.finish();
+ assertFalse(activity.isConfigurationDispatchPaused());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index 5187f87cd6db..42752c326615 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -217,6 +217,19 @@ public class TransparentPolicyTest extends WindowTestsBase {
});
}
+ @Test
+ public void testNotApplyStrategyToTranslucentActivitiesOverNotLetterboxedActivities() {
+ runTestScenario((robot) -> {
+ robot.transparentActivity((ta) -> {
+ ta.activity().setTopActivityHasLetterboxedBounds(false);
+ ta.launchTransparentActivityInTask();
+
+ ta.checkTopActivityTransparentPolicyStartNotInvoked();
+ ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ false);
+ });
+ });
+ }
+
@EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
@Test
public void testNotRunStrategyToTranslucentActivitiesIfRespectOrientation() {
@@ -388,6 +401,7 @@ public class TransparentPolicyTest extends WindowTestsBase {
mTransparentActivityRobot = new AppCompatTransparentActivityRobot(activity());
// We always create at least an opaque activity in a Task
activity().createNewTaskWithBaseActivity();
+ activity().setTopActivityHasLetterboxedBounds(true);
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 401964c2f597..1fa657822189 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -23,6 +23,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -36,6 +40,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -80,6 +85,8 @@ import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.InsetsFrameProvider;
@@ -103,6 +110,7 @@ import org.mockito.Mockito;
import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
import java.util.NoSuchElementException;
@@ -967,7 +975,7 @@ public class WindowContainerTests extends WindowTestsBase {
Rect insetsRect = new Rect(0, 200, 1080, 700);
final int flags = FLAG_FORCE_CONSUMING;
final InsetsFrameProvider provider =
- new InsetsFrameProvider(owner, 1, WindowInsets.Type.captionBar())
+ new InsetsFrameProvider(owner, 1, captionBar())
.setArbitraryRectangle(insetsRect)
.setFlags(flags);
task.addLocalInsetsFrameProvider(provider, owner);
@@ -1678,6 +1686,178 @@ public class WindowContainerTests extends WindowTestsBase {
assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
}
+ @Test
+ public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(0, child1.mMergedExcludeInsetsTypes);
+ assertEquals(0, child11.mMergedExcludeInsetsTypes);
+ assertEquals(0, child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ child1.setExcludeInsetsTypes(ime());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ child11.setExcludeInsetsTypes(navigationBars());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ // overwriting the same value has no change
+ for (int i = 0; i < 2; i++) {
+ root.setExcludeInsetsTypes(statusBars());
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(),
+ child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+ }
+
+ // set and reset type statusBars on child. Should have no effect because of parent
+ child2.setExcludeInsetsTypes(statusBars());
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+ // reset
+ child2.setExcludeInsetsTypes(0);
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+ // when parent has statusBars also removed, it should be cleared from all children in the
+ // hierarchy
+ root.setExcludeInsetsTypes(0);
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ // change on node should have no effect on siblings
+ child12.setExcludeInsetsTypes(captionBar());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(captionBar() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSetExcludeInsetsTypes_appliedAfterReparenting() {
+ final SurfaceControl mockSurfaceControl = mock(SurfaceControl.class);
+ final DisplayContent mockDisplayContent = mock(DisplayContent.class);
+ final var mockInsetsStateController = mock(InsetsStateController.class);
+ doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+ final WindowContainer root1 = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ final WindowContainer root2 = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ final WindowContainer child = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ doNothing().when(child).onConfigurationChanged(any());
+
+ root1.setExcludeInsetsTypes(ime());
+ root2.setExcludeInsetsTypes(captionBar());
+ assertEquals(ime(), root1.mMergedExcludeInsetsTypes);
+ assertEquals(captionBar(), root2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child.mMergedExcludeInsetsTypes);
+ clearInvocations(mockInsetsStateController);
+
+ root1.addChild(child, 0);
+ assertEquals(ime(), child.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // Make sure that reparenting does not call notifyInsetsChanged twice
+ child.reparent(root2, 0);
+ assertEquals(captionBar(), child.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child.asWindowState())));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSetExcludeInsetsTypes_notifyInsetsAfterChange() {
+ final var mockDisplayContent = mock(DisplayContent.class);
+ final var mockInsetsStateController = mock(InsetsStateController.class);
+ doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final WindowState mockRootWs = mock(WindowState.class);
+ final TestWindowContainer root = builder.setLayer(0).setAsWindowState(mockRootWs).build();
+ root.mDisplayContent = mockDisplayContent;
+ verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+
+ root.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(root.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // adding a child (while parent has set excludedInsetsTypes) should trigger
+ // notifyInsetsChanged
+ final WindowState mockChildWs = mock(WindowState.class);
+ final TestWindowContainer child1 = builder.setLayer(0).setAsWindowState(
+ mockChildWs).build();
+ child1.mDisplayContent = mockDisplayContent;
+ root.addChildWindow(child1);
+ // TestWindowContainer overrides onParentChanged and therefore doesn't call into
+ // mergeExcludeInsetsTypesAndNotifyInsetsChanged. This is checked in another test
+ assertTrue(child1.mOnParentChangedCalled);
+ child1.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child1.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // not changing excludedInsetsTypes should not trigger notifyInsetsChanged again
+ root.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+ }
+
+ private WindowContainer<?> createWindowContainerSpy(SurfaceControl mockSurfaceControl,
+ DisplayContent mockDisplayContent) {
+ final WindowContainer<?> wc = spy(new WindowContainer<>(mWm));
+ final WindowState mocWs = mock(WindowState.class);
+ doReturn(mocWs).when(wc).asWindowState();
+ wc.mSurfaceControl = mockSurfaceControl;
+ wc.mDisplayContent = mockDisplayContent;
+ return wc;
+ }
+
private static boolean hasLocalSource(WindowContainer container, int sourceId) {
if (container.mLocalInsetsSources == null) {
return false;
@@ -1693,6 +1873,7 @@ public class WindowContainerTests extends WindowTestsBase {
private boolean mFillsParent;
private boolean mWaitForTransitStart;
private Integer mOrientation;
+ private WindowState mWindowState;
private boolean mOnParentChangedCalled;
private boolean mOnDescendantOverrideCalled;
@@ -1714,7 +1895,7 @@ public class WindowContainerTests extends WindowTestsBase {
};
TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating,
- boolean isVisible, boolean waitTransitStart, Integer orientation) {
+ boolean isVisible, boolean waitTransitStart, Integer orientation, WindowState ws) {
super(wm);
mLayer = layer;
@@ -1723,6 +1904,7 @@ public class WindowContainerTests extends WindowTestsBase {
mFillsParent = true;
mOrientation = orientation;
mWaitForTransitStart = waitTransitStart;
+ mWindowState = ws;
spyOn(mSurfaceAnimator);
doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating();
doReturn(ANIMATION_TYPE_APP_TRANSITION).when(mSurfaceAnimator).getAnimationType();
@@ -1790,6 +1972,11 @@ public class WindowContainerTests extends WindowTestsBase {
boolean isWaitingForTransitionStart() {
return mWaitForTransitStart;
}
+
+ @Override
+ WindowState asWindowState() {
+ return mWindowState;
+ }
}
private static class TestWindowContainerBuilder {
@@ -1799,6 +1986,7 @@ public class WindowContainerTests extends WindowTestsBase {
private boolean mIsVisible;
private boolean mIsWaitTransitStart;
private Integer mOrientation;
+ private WindowState mWindowState;
TestWindowContainerBuilder(WindowManagerService wm) {
mWm = wm;
@@ -1806,6 +1994,7 @@ public class WindowContainerTests extends WindowTestsBase {
mIsAnimating = false;
mIsVisible = false;
mOrientation = null;
+ mWindowState = null;
}
TestWindowContainerBuilder setLayer(int layer) {
@@ -1828,6 +2017,11 @@ public class WindowContainerTests extends WindowTestsBase {
return this;
}
+ TestWindowContainerBuilder setAsWindowState(WindowState ws) {
+ mWindowState = ws;
+ return this;
+ }
+
TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) {
mIsWaitTransitStart = waitTransitStart;
return this;
@@ -1835,7 +2029,7 @@ public class WindowContainerTests extends WindowTestsBase {
TestWindowContainer build() {
return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible,
- mIsWaitTransitStart, mOrientation);
+ mIsWaitTransitStart, mOrientation, mWindowState);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index d183cf720491..f3a2e86fe6db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -46,6 +46,7 @@ import android.app.servertransaction.WindowContextInfoChangeItem;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayInfo;
@@ -54,7 +55,12 @@ import android.window.WindowTokenClient;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
+import com.google.common.truth.Expect;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -65,6 +71,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+
/**
* Build/Install/Run:
* atest WmTests:WindowContextListenerControllerTests
@@ -86,6 +93,9 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
private WindowProcessController mWpc;
private WindowContainer<?> mContainer;
+ @Rule
+ public final Expect mExpect = Expect.create();
+
@Before
public void setUp() {
initMocks(this);
@@ -341,6 +351,30 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId);
}
+ @Test
+ @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void assertCallerCanReparentListener_returnsTrueWhenExpected() {
+ mController.registerWindowContainerListener(mWpc, mClientToken, mContainer,
+ TYPE_APPLICATION_OVERLAY, null /* options */);
+
+ // Here there are several checks in one test as wm tests are expensive.
+
+ // Correct conditions -> returns true
+ mExpect.that(mController.assertCallerCanReparentListener(mClientToken,
+ /* callerCanManageAppTokens= */ true,
+ /* callingUid= */ mWpc.mUid,
+ /* displayId= */ DEFAULT_DISPLAY + 1
+ )).isTrue();
+
+ // sameDisplayId (so, container already attached) -> returnsFalse
+ mExpect.that(mController.assertCallerCanReparentListener(
+ mClientToken,
+ /* callerCanManageAppTokens= */ true,
+ /* callingUid= */ mWpc.mUid,
+ /* displayId= */ DEFAULT_DISPLAY // <- same display ID
+ )).isFalse();
+ }
+
private static class TestWindowTokenClient extends WindowTokenClient {
private Configuration mConfiguration;
private int mDisplayId;
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 6111a658a048..69df66ee783b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -29,6 +29,7 @@ import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -38,15 +39,16 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -69,6 +71,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -76,6 +79,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityThread;
import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -84,19 +88,18 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
import android.view.IWindow;
import android.view.InputChannel;
+import android.view.InputDevice;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInsets;
@@ -150,9 +153,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
@Rule
public Expect mExpect = Expect.create();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
@After
public void tearDown() {
mWm.mSensitiveContentPackages.clearBlockedApps();
@@ -1138,6 +1138,53 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testUpdateInputChannel_sanitizeWithoutPermission_ThrowsError() {
+ final Session session = mock(Session.class);
+ final int callingUid = Process.FIRST_APPLICATION_UID;
+ final int callingPid = 1234;
+ final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final IBinder window = new Binder();
+ final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
+
+
+ final InputChannel inputChannel = new InputChannel();
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
+ surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
+ TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
+ "TestInputChannel", inputChannel));
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testUpdateInputChannel_sanitizeWithPermission_doesNotThrowError() {
+ final Session session = mock(Session.class);
+ final int callingUid = Process.FIRST_APPLICATION_UID;
+ final int callingPid = 1234;
+ final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final IBinder window = new Binder();
+ final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
+
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mWm.mContext).checkPermission(
+ android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+ callingPid,
+ callingUid);
+
+ final InputChannel inputChannel = new InputChannel();
+
+ mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+ window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */,
+ INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
+ TYPE_APPLICATION, null /* windowToken */, inputTransferToken, "TestInputChannel",
+ inputChannel);
+ }
+
+ @Test
public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() {
final Session session = mock(Session.class);
final int callingUid = Process.SYSTEM_UID;
@@ -1191,55 +1238,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_PRIVACY) != 0));
}
- @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
- @Test
- public void testDrawMagnifiedViewport() {
- final int displayId = mDisplayContent.mDisplayId;
- // Use real surface, so ViewportWindow's BlastBufferQueue can be created.
- final ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
- mWm.mSurfaceControlFactory = () -> new SurfaceControl.Builder() {
- @Override
- public SurfaceControl build() {
- final SurfaceControl sc = super.build();
- surfaceControls.add(sc);
- return sc;
- }
- };
- mWm.mAccessibilityController.setMagnificationCallbacks(displayId,
- mock(WindowManagerInternal.MagnificationCallbacks.class));
- final boolean[] lockCanvasInWmLock = { false };
- final Surface surface = mWm.mAccessibilityController.forceShowMagnifierSurface(displayId);
- spyOn(surface);
- doAnswer(invocationOnMock -> {
- lockCanvasInWmLock[0] |= Thread.holdsLock(mWm.mGlobalLock);
- invocationOnMock.callRealMethod();
- return null;
- }).when(surface).lockCanvas(any());
- mWm.mAccessibilityController
- .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
- waitUntilHandlersIdle();
- try {
- verify(surface).lockCanvas(any());
-
- clearInvocations(surface);
- // Invalidate and redraw.
- mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
- mWm.mAccessibilityController
- .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
- // Turn off magnification to release surface.
- mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
- waitUntilHandlersIdle();
- // lockCanvas must not be called after releasing.
- verify(surface, never()).lockCanvas(any());
- verify(surface).release();
- assertFalse(lockCanvasInWmLock[0]);
- } finally {
- for (int i = surfaceControls.size() - 1; i >= 0; --i) {
- surfaceControls.get(i).release();
- }
- }
- }
-
@Test
public void testRequestKeyboardShortcuts_noWindow() {
doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
@@ -1275,6 +1273,48 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ public void testInputDeviceNotifyConfigurationChanged() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_FILTER_IRRELEVANT_INPUT_DEVICE_CHANGE);
+ spyOn(mDisplayContent);
+ doReturn(false).when(mDisplayContent).sendNewConfiguration();
+ final InputDevice deviceA = mock(InputDevice.class);
+ final InputDevice deviceB = mock(InputDevice.class);
+ doReturn("deviceA").when(deviceA).getDescriptor();
+ doReturn("deviceB").when(deviceB).getDescriptor();
+ final InputDevice[] devices1 = { deviceA };
+ final InputDevice[] devices2 = { deviceB, deviceA };
+ final Runnable verifySendNewConfiguration = () -> {
+ clearInvocations(mDisplayContent);
+ mWm.mInputManagerCallback.notifyConfigurationChanged();
+ verify(mDisplayContent).sendNewConfiguration();
+ };
+ doReturn(devices1).when(mWm.mInputManager).getInputDevices();
+ verifySendNewConfiguration.run();
+
+ doReturn(devices2).when(mWm.mInputManager).getInputDevices();
+ verifySendNewConfiguration.run();
+
+ doReturn(true).when(deviceB).isEnabled();
+ verifySendNewConfiguration.run();
+
+ doReturn(true).when(deviceA).isExternal();
+ verifySendNewConfiguration.run();
+
+ doReturn(1).when(deviceA).getSources();
+ verifySendNewConfiguration.run();
+
+ doReturn(1).when(deviceA).getAssociatedDisplayId();
+ verifySendNewConfiguration.run();
+
+ doReturn(1).when(deviceA).getKeyboardType();
+ verifySendNewConfiguration.run();
+
+ clearInvocations(mDisplayContent);
+ mWm.mInputManagerCallback.notifyConfigurationChanged();
+ verify(mDisplayContent, never()).sendNewConfiguration();
+ }
+
+ @Test
public void testReportSystemGestureExclusionChanged_invalidWindow() {
final Session session = mock(Session.class);
final IWindow window = mock(IWindow.class);
@@ -1371,6 +1411,84 @@ public class WindowManagerServiceTests extends WindowTestsBase {
assertThat(result).isEqualTo(WindowManagerGlobal.ADD_INVALID_DISPLAY);
}
+ /** Mocks some deps to associate a display content to a specific display id. */
+ private void setupReparentWindowContextToDisplayAreaTest(WindowToken windowToken,
+ DisplayContent dc, int displayId) {
+ spyOn(mWm.mWindowContextListenerController);
+ doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ doReturn(true).when(mWm.mWindowContextListenerController).assertCallerCanReparentListener(
+ any(), anyBoolean(), anyInt(), eq(displayId));
+ doReturn(windowToken).when(mWm.mWindowContextListenerController).getContainer(
+ eq(windowToken.token));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void reparentWindowContextToDisplayArea_newDisplay_reparented() {
+ final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE,
+ mDisplayContent);
+ final int newDisplayId = 1;
+ final DisplayContent dc = createNewDisplay();
+ setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId);
+
+ assertThat(windowToken.getDisplayContent()).isEqualTo(mDisplayContent);
+
+ assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token,
+ newDisplayId)).isTrue();
+
+ assertThat(windowToken.getDisplayContent()).isNotEqualTo(mDisplayContent);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void reparentWindowContextToDisplayArea_newDisplayButFlagDisabled_notReparented() {
+ final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE,
+ mDisplayContent);
+ final int newDisplayId = 1;
+ final DisplayContent dc = createNewDisplay();
+ setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId);
+
+ assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token,
+ newDisplayId)).isFalse();
+
+ assertThat(windowToken.getDisplayContent()).isEqualTo(mDisplayContent);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void reparentWindowContext_afterReparent_DCNeedsLayout() {
+ final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE,
+ mDisplayContent);
+ final int newDisplayId = 1;
+ final DisplayContent dc = createNewDisplay();
+ setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId);
+
+ assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token,
+ newDisplayId)).isTrue();
+
+ assertThat(dc.isLayoutNeeded()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void reparentWindowContext_afterReparent_traversalScheduled() {
+ final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE,
+ mDisplayContent);
+ final int newDisplayId = 1;
+ final DisplayContent dc = createNewDisplay();
+ setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId);
+ spyOn(mWm.mWindowPlacerLocked);
+ reset(mWm.mWindowPlacerLocked);
+
+ verify(mWm.mWindowPlacerLocked, never()).requestTraversal();
+
+ assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token,
+ newDisplayId)).isTrue();
+
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
+ }
+
+
class TestResultReceiver implements IResultReceiver {
public android.os.Bundle resultData;
private final IBinder mBinder = mock(IBinder.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index f56825faab73..410fa2879600 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.app.ActivityManager.START_CANCELED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -28,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
@@ -37,6 +40,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -61,6 +65,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.quality.Strictness.LENIENT;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -69,6 +74,7 @@ import android.app.ActivityOptions;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IRequestFinishCallback;
import android.app.PictureInPictureParams;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
@@ -78,6 +84,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.util.ArrayMap;
import android.util.Rational;
import android.view.Display;
@@ -87,6 +94,7 @@ import android.view.WindowInsets;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.IWindowContainerTransactionCallback;
+import android.window.RemoteTransition;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -102,6 +110,7 @@ import com.android.window.flags.Flags;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.HashSet;
@@ -114,6 +123,8 @@ import java.util.function.BiConsumer;
* Build/Install/Run:
* atest WmTests:WindowOrganizerTests
*/
+
+// TODO revert parts of this set to set the flag to test the behavior
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
@@ -533,6 +544,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
public void testSetActivityWindowingMode() {
final ActivityRecord record = makePipableActivity();
final Task rootTask = record.getRootTask();
@@ -638,6 +650,66 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ public void testStartActivityInTaskFragment_checkCallerPermission() {
+ final ActivityStartController activityStartController =
+ mWm.mAtmService.getActivityStartController();
+ spyOn(activityStartController);
+ final ArgumentCaptor<SafeActivityOptions> activityOptionsCaptor =
+ ArgumentCaptor.forClass(SafeActivityOptions.class);
+
+ final int uid = Binder.getCallingUid();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ final TaskFragmentOrganizer organizer =
+ createTaskFragmentOrganizer(t, true /* isSystemOrganizer */);
+ final IBinder token = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(rootTask)
+ .setFragmentToken(token)
+ .setOrganizer(organizer)
+ .createActivityCount(1)
+ .build();
+ mWm.mAtmService.mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment);
+ final ActivityRecord ownerActivity = taskFragment.getTopMostActivity();
+
+ // Start Activity in TaskFragment with remote transition.
+ final RemoteTransition transition = mock(RemoteTransition.class);
+ final ActivityOptions options = ActivityOptions.makeRemoteTransition(transition);
+ final Intent intent = new Intent();
+ t.startActivityInTaskFragment(token, ownerActivity.token, intent, options.toBundle());
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN,
+ false /* shouldApplyIndependently */, null /* remoteTransition */);
+
+ // Get the ActivityOptions.
+ verify(activityStartController).startActivityInTaskFragment(
+ eq(taskFragment), eq(intent), activityOptionsCaptor.capture(),
+ eq(ownerActivity.token), eq(uid), anyInt(), any());
+ final SafeActivityOptions safeActivityOptions = activityOptionsCaptor.getValue();
+
+ final MockitoSession session =
+ mockitoSession().strictness(LENIENT).spyStatic(ActivityTaskManagerService.class)
+ .startMocking();
+ try {
+ // Without the CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission, start activity with
+ // remote transition is not allowed.
+ doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission(
+ eq(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS), anyInt(), eq(uid)));
+ assertThrows(SecurityException.class,
+ () -> safeActivityOptions.getOptions(mWm.mAtmService.mTaskSupervisor));
+
+ // With the CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission, start activity with
+ // remote transition is allowed.
+ doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
+ eq(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS), anyInt(), eq(uid)));
+ safeActivityOptions.getOptions(mWm.mAtmService.mTaskSupervisor);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
public void testTaskFragmentChangeHidden_throwsWhenNotSystemOrganizer() {
// Non-system organizers are not allow to update the hidden state.
testTaskFragmentChangesWithoutSystemOrganizerThrowException(
@@ -1231,6 +1303,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
public void testEnterPipParams() {
final StubOrganizer o = new StubOrganizer();
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
@@ -1246,6 +1319,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
public void testChangePipParams() {
class ChangeSavingOrganizer extends StubOrganizer {
RunningTaskInfo mChangedInfo;
@@ -1817,6 +1891,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
@SuppressWarnings("GuardedBy")
@Test
+ @RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
public void testResumeTopsWhenLeavingPinned() {
final ActivityRecord home = new ActivityBuilder(mAtm).setTask(
mRootWindowContainer.getDefaultTaskDisplayArea().getRootHomeTask()).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 9bad2ec2ca2a..1dfb20a41816 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -309,6 +309,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransactionItems(eq(thread), any());
verify(mClientLifecycleManager, never()).scheduleTransactionItemNow(eq(thread), any());
// Cached -> non-cached will send the previous deferred config immediately.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 2d5e5dacc217..50e0e181cd68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -71,6 +71,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -84,12 +85,14 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
import android.os.IBinder;
import android.os.InputConfig;
import android.os.RemoteException;
@@ -97,6 +100,7 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.Gravity;
@@ -353,6 +357,29 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
+ public void testDestroySurface() {
+ final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ win.mHasSurface = win.mAnimatingExit = true;
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
+ win.onExitAnimationDone();
+
+ assertFalse("Case 1 destroySurface no-op",
+ win.destroySurface(false /* cleanupOnResume */, false /* appStopped */));
+ assertTrue(win.mHasSurface);
+ assertTrue(win.mDestroying);
+
+ assertFalse("Case 2 destroySurface no-op",
+ win.destroySurface(true /* cleanupOnResume */, false /* appStopped */));
+ assertTrue(win.mHasSurface);
+ assertTrue(win.mDestroying);
+
+ assertTrue("Case 3 destroySurface destroys surface",
+ win.destroySurface(false /* cleanupOnResume */, true /* appStopped */));
+ assertFalse(win.mDestroying);
+ assertFalse(win.mHasSurface);
+ }
+
+ @Test
public void testPrepareWindowToDisplayDuringRelayout() {
// Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON before
// calling setCurrentLaunchCanTurnScreenOn for windows with flag in the same activity.
@@ -406,11 +433,11 @@ public class WindowStateTests extends WindowTestsBase {
final var powerManager = mWm.mPowerManager;
clearInvocations(powerManager);
firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString(), anyInt());
clearInvocations(powerManager);
secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString(), anyInt());
}
private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow,
@@ -420,9 +447,9 @@ public class WindowStateTests extends WindowTestsBase {
appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */);
if (expectedWakeupCalled) {
- verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString(), anyInt());
} else {
- verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString(), anyInt());
}
// If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false
// because the state will be consumed.
@@ -483,6 +510,32 @@ public class WindowStateTests extends WindowTestsBase {
assertFalse(statusBar.isVisible());
}
+ /**
+ * Verifies that the InsetsSourceProvider frame cannot be updated by WindowState before
+ * relayout is called.
+ */
+ @SetupWindows(addWindows = { W_STATUS_BAR })
+ @Test
+ public void testUpdateSourceFrameBeforeRelayout() {
+ final WindowState statusBar = mStatusBarWindow;
+ statusBar.mHasSurface = true;
+ assertTrue(statusBar.isVisible());
+ final int statusBarId = InsetsSource.createId(null, 0, statusBars());
+ final var statusBarProvider = mDisplayContent.getInsetsStateController()
+ .getOrCreateSourceProvider(statusBarId, statusBars());
+ statusBarProvider.setWindowContainer(statusBar, null /* frameProvider */,
+ null /* imeFrameProvider */);
+
+ statusBar.updateSourceFrame(new Rect(0, 0, 500, 200));
+ assertTrue("InsetsSourceProvider frame should not be updated before relayout",
+ statusBarProvider.getSourceFrame().isEmpty());
+
+ makeWindowVisible(statusBar);
+ statusBar.updateSourceFrame(new Rect(0, 0, 500, 100));
+ assertEquals("InsetsSourceProvider frame should be updated after relayout",
+ new Rect(0, 0, 500, 100), statusBarProvider.getSourceFrame());
+ }
+
@Test
public void testIsSelfOrAncestorWindowAnimating() {
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
@@ -1508,6 +1561,57 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
+ public void testIsSecureLocked_flagSecureSet() {
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+ window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+
+ assertTrue(window.isSecureLocked());
+ }
+
+ @Test
+ public void testIsSecureLocked_flagSecureNotSet() {
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+
+ assertFalse(window.isSecureLocked());
+ }
+
+ @Test
+ public void testIsSecureLocked_disableSecureWindows() {
+ assumeTrue(Build.IS_DEBUGGABLE);
+
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+ window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+ ContentResolver cr = useFakeSettingsProvider();
+
+ // isSecureLocked should return false when DISABLE_SECURE_WINDOWS is set to 1
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertFalse(window.isSecureLocked());
+
+ // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is set to 0.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "0");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertTrue(window.isSecureLocked());
+
+ // Disable secure windows again.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertFalse(window.isSecureLocked());
+
+ // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is deleted.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, null);
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertTrue(window.isSecureLocked());
+ }
+
+ @Test
@RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
String testPackage = "test";
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a215c0a80b46..3a97cc621e0d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1027,7 +1027,8 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
@Override
- public void setImeInputTargetRequestedVisibility(boolean visible) {
+ public void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
}
};
}
@@ -1123,6 +1124,15 @@ public class WindowTestsBase extends SystemServiceTestsBase {
displayContent.onRequestedOverrideConfigurationChanged(c);
}
+ static void makeDisplayLargeScreen(DisplayContent displayContent) {
+ final int swDp = displayContent.getConfiguration().smallestScreenWidthDp;
+ if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
+ final int height = 100 + (int) (displayContent.getDisplayMetrics().density
+ * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
+ resizeDisplay(displayContent, 100 + height, height);
+ }
+ }
+
/** Used for the tests that assume the display is portrait by default. */
static void makeDisplayPortrait(DisplayContent displayContent) {
if (displayContent.mBaseDisplayHeight <= displayContent.mBaseDisplayWidth) {
@@ -1223,7 +1233,14 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
static ComponentName getUniqueComponentName() {
- return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+ return getUniqueComponentName(DEFAULT_COMPONENT_PACKAGE_NAME);
+ }
+
+ static ComponentName getUniqueComponentName(String packageName) {
+ if (packageName == null) {
+ packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
+ }
+ return ComponentName.createRelative(packageName,
DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
}
@@ -1298,8 +1315,7 @@ public class WindowTestsBase extends SystemServiceTestsBase {
ActivityBuilder setActivityTheme(int theme) {
mActivityTheme = theme;
// Use the real package of test so it can get a valid context for theme.
- mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
- DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+ mComponent = getUniqueComponentName(mService.mContext.getPackageName());
return this;
}
@@ -1743,7 +1759,7 @@ public class WindowTestsBase extends SystemServiceTestsBase {
if (mIntent == null) {
mIntent = new Intent();
if (mComponent == null) {
- mComponent = getUniqueComponentName();
+ mComponent = getUniqueComponentName(mPackage);
}
mIntent.setComponent(mComponent);
mIntent.setFlags(mFlags);
@@ -2023,9 +2039,22 @@ public class WindowTestsBase extends SystemServiceTestsBase {
return new TestWindowToken(type, dc, persistOnEmpty);
}
+ static TestWindowToken createTestClientWindowToken(int type, DisplayContent dc) {
+ SystemServicesTestRule.checkHoldsLock(dc.mWmService.mGlobalLock);
+
+ return new TestWindowToken(type, dc, false /* persistOnEmpty */, true /* fromClient */);
+ }
+
/** Used so we can gain access to some protected members of the {@link WindowToken} class */
static class TestWindowToken extends WindowToken {
+ private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty,
+ boolean fromClient) {
+ super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc,
+ false /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
+ fromClient /* fromClientToken */, null /* options */);
+ }
+
private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) {
super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc,
false /* ownerCanManageAppTokens */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 714eb4b3c093..f226b9d29ca0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
@@ -32,21 +33,27 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.WindowInsets;
import android.window.WindowContext;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mockito;
import java.util.function.BiFunction;
@@ -157,7 +164,16 @@ public class WindowTokenTests extends WindowTestsBase {
// Verify that the other token window is still around.
assertEquals(1, token.getWindowsCount());
+ final TransitionController transitionController = token.mTransitionController;
+ spyOn(transitionController);
+ doReturn(true).when(transitionController).isPlayingTarget(token);
window2.removeImmediately();
+ assertTrue(token.mIsExiting);
+ assertNotNull("Defer removal for playing transition", token.getParent());
+
+ doReturn(false).when(transitionController).isPlayingTarget(token);
+ token.handleCompleteDeferredRemoval();
+ assertFalse(token.mIsExiting);
// Verify that the token is no-longer attached to its parent
assertNull(token.getParent());
// Verify that the token windows are no longer attached to it.
@@ -294,14 +310,39 @@ public class WindowTokenTests extends WindowTestsBase {
// immediately. verify the window will hide without applying exit animation.
mWm.removeWindowToken(win.mToken.token, false /* removeWindows */, false /* animateExit */,
mDisplayContent.mDisplayId);
- verify(win).onSetAppExiting(Mockito.eq(false) /* animateExit */);
+ verify(win).onSetAppExiting(eq(false) /* animateExit */);
verify(win).hide(false /* doAnimation */, false /* requestAnim */);
assertFalse(win.isOnScreen());
- verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false);
+ verify(win.mWinAnimator, never()).applyAnimationLocked(TRANSIT_EXIT, false);
assertTrue(win.mToken.hasChild());
// Even though the window is being removed afterwards, it won't apply exit animation.
win.removeIfPossible();
- verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false);
+ verify(win.mWinAnimator, never()).applyAnimationLocked(TRANSIT_EXIT, false);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void onDisplayChanged_differentDisplay_reparented() {
+ final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
+ final DisplayContent dc = mock(DisplayContent.class);
+ when(dc.getWindowToken(any())).thenReturn(null); // dc doesn't have this window token.
+
+ token.onDisplayChanged(dc);
+
+ verify(dc).reParentWindowToken(eq(token));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
+ public void onDisplayChanged_samedisplay_notReparented() {
+
+ final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
+ final DisplayContent dc = mock(DisplayContent.class);
+ when(dc.getWindowToken(any())).thenReturn(mock(WindowToken.class));
+
+ token.onDisplayChanged(dc);
+
+ verify(dc, never()).reParentWindowToken(eq(token));
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 010a32274dcd..e7c9e927b311 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -48,6 +48,7 @@ import android.app.IUidObserver;
import android.app.PendingIntent;
import android.app.UidObserver;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
import android.app.usage.BroadcastResponseStatsList;
@@ -230,6 +231,7 @@ public class UsageStatsService extends SystemService implements
// Do not use directly. Call getDpmInternal() instead
DevicePolicyManagerInternal mDpmInternal;
// Do not use directly. Call getShortcutServiceInternal() instead
+ SupervisionManagerInternal mSupervisionManagerInternal;
ShortcutServiceInternal mShortcutServiceInternal;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
@@ -439,6 +441,9 @@ public class UsageStatsService extends SystemService implements
// initialize mDpmInternal
getDpmInternal();
// initialize mShortcutServiceInternal
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ getSupervisionManagerInternal();
+ }
getShortcutServiceInternal();
mResponseStatsTracker.onSystemServicesReady(getContext());
@@ -604,6 +609,15 @@ public class UsageStatsService extends SystemService implements
return mDpmInternal;
}
+ @Nullable
+ private SupervisionManagerInternal getSupervisionManagerInternal() {
+ if (mSupervisionManagerInternal == null) {
+ mSupervisionManagerInternal =
+ LocalServices.getService(SupervisionManagerInternal.class);
+ }
+ return mSupervisionManagerInternal;
+ }
+
private ShortcutServiceInternal getShortcutServiceInternal() {
if (mShortcutServiceInternal == null) {
mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
@@ -753,6 +767,16 @@ public class UsageStatsService extends SystemService implements
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
}
+ private boolean isSupervisionEnabled(int callingUid) {
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ SupervisionManagerInternal smInternal = getSupervisionManagerInternal();
+ return smInternal != null && smInternal.isActiveSupervisionApp(callingUid);
+ } else {
+ DevicePolicyManagerInternal dpmInternal = getDpmInternal();
+ return dpmInternal != null && dpmInternal.isActiveSupervisionApp(callingUid);
+ }
+ }
+
private static void deleteRecursively(final File path) {
if (path.isDirectory()) {
final File[] files = path.listFiles();
@@ -2929,10 +2953,9 @@ public class UsageStatsService extends SystemService implements
long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent,
String callingPackage) {
final int callingUid = Binder.getCallingUid();
- final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (!hasPermissions(
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
- && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
+ && !isSupervisionEnabled(callingUid)) {
throw new SecurityException("Caller must be the active supervision app or "
+ "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
}
@@ -2956,10 +2979,9 @@ public class UsageStatsService extends SystemService implements
@Override
public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
final int callingUid = Binder.getCallingUid();
- final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (!hasPermissions(
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
- && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
+ && !isSupervisionEnabled(callingUid)) {
throw new SecurityException("Caller must be the active supervision app or "
+ "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
}
diff --git a/services/usb/java/com/UsbDataSignalDisableRequesters.java b/services/usb/java/com/UsbDataSignalDisableRequesters.java
new file mode 100644
index 000000000000..d4d6492ab984
--- /dev/null
+++ b/services/usb/java/com/UsbDataSignalDisableRequesters.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.util.ArraySet;
+
+/**
+ * A helper class to store and manage the request for disabling USB port data signaling.
+ *
+ * External requesters are identified by UIDs.
+ * Internal requesters are identified by a reason code enumerated in UsbManagerInternal.
+ *
+ * @hide
+ */
+public final class UsbDataSignalDisableRequesters {
+ final ArraySet<Integer> mExternalUids = new ArraySet<>();
+ final ArraySet<Integer> mInternalReasons = new ArraySet<>();
+
+ public boolean isEmpty() {
+ return mExternalUids.isEmpty() && mInternalReasons.isEmpty();
+ }
+} \ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 5ef0fe346dc6..d976f5ba1af2 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -337,7 +337,7 @@ public final class UsbAlsaManager {
deviceAddress, hasOutput, hasInput,
isInputHeadset, isOutputHeadset, isDock);
alsaDevice.setDeviceNameAndDescription(
- cardRec.getCardName(), cardRec.getCardDescription());
+ usbDevice.getProductName(), cardRec.getCardDescription());
if (IS_MULTI_MODE) {
deselectCurrentDevice(alsaDevice.getInputDeviceType());
deselectCurrentDevice(alsaDevice.getOutputDeviceType());
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 6c1e1a428fb8..15c8b135d2c4 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -185,6 +185,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
private static final int MSG_INCREASE_SENDSTRING_COUNT = 21;
private static final int MSG_UPDATE_USB_SPEED = 22;
private static final int MSG_UPDATE_HAL_VERSION = 23;
+ private static final int MSG_USER_UNLOCKED_AFTER_BOOT = 24;
// Delay for debouncing USB disconnects.
// We often get rapid connect/disconnect events when enabling USB functions,
@@ -414,6 +415,17 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
}
};
+ if (Flags.checkUserActionUnlocked()) {
+ BroadcastReceiver userUnlockedAfterBootReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.sendEmptyMessage(MSG_USER_UNLOCKED_AFTER_BOOT);
+ }
+ };
+ mContext.registerReceiver(userUnlockedAfterBootReceiver,
+ new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+ }
+
mContext.registerReceiver(portReceiver,
new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));
mContext.registerReceiver(chargingReceiver,
@@ -474,6 +486,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
}
+ // Same as ACTION_LOCKED_BOOT_COMPLETED.
public void bootCompleted() {
if (DEBUG) Slog.d(TAG, "boot completed");
mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
@@ -632,7 +645,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
protected int mUsbSpeed;
protected int mCurrentGadgetHalVersion;
protected boolean mPendingBootAccessoryHandshakeBroadcast;
-
+ protected boolean mUserUnlockedAfterBoot;
/**
* The persistent property which stores whether adb is enabled or not.
* May also contain vendor-specific default functions for testing purposes.
@@ -837,6 +850,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
return !userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
}
+ private void attachAccessory() {
+ mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
+ removeMessages(MSG_ACCESSORY_HANDSHAKE_TIMEOUT);
+ broadcastUsbAccessoryHandshake();
+ }
+
private void updateCurrentAccessory() {
// We are entering accessory mode if we have received a request from the host
// and the request has not timed out yet.
@@ -863,10 +882,13 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
// defer accessoryAttached if system is not ready
- if (mBootCompleted) {
- mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
- removeMessages(MSG_ACCESSORY_HANDSHAKE_TIMEOUT);
- broadcastUsbAccessoryHandshake();
+ if (!Flags.checkUserActionUnlocked() && mBootCompleted) {
+ attachAccessory();
+ }
+ // Defer accessoryAttached till user unlocks after boot.
+ // When no pin pattern is set, ACTION_USER_UNLOCKED would fire anyways
+ if (Flags.checkUserActionUnlocked() && mUserUnlockedAfterBoot) {
+ attachAccessory();
} // else handle in boot completed
} else {
Slog.e(TAG, "nativeGetAccessoryStrings failed");
@@ -887,7 +909,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
if (mCurrentAccessory != null) {
- if (mBootCompleted) {
+ if (!Flags.checkUserActionUnlocked() && mBootCompleted) {
+ mPermissionManager.usbAccessoryRemoved(mCurrentAccessory);
+ }
+ if (Flags.checkUserActionUnlocked() && mUserUnlockedAfterBoot) {
mPermissionManager.usbAccessoryRemoved(mCurrentAccessory);
}
mCurrentAccessory = null;
@@ -1027,28 +1052,36 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
if (enabled != mMidiEnabled) {
if (enabled) {
+ boolean midiDeviceFound = false;
if (android.hardware.usb.flags.Flags.enableUsbSysfsMidiIdentification()) {
try {
getMidiCardDevice();
+ midiDeviceFound = true;
} catch (FileNotFoundException e) {
- Slog.e(TAG, "could not identify MIDI device", e);
- enabled = false;
+ Slog.w(TAG, "could not identify MIDI device", e);
}
- } else {
+ }
+ // For backward compatibility with older kernels without
+ // https://lore.kernel.org/r/20240307030922.3573161-1-royluo@google.com
+ if (!midiDeviceFound) {
Scanner scanner = null;
try {
scanner = new Scanner(new File(MIDI_ALSA_PATH));
mMidiCard = scanner.nextInt();
mMidiDevice = scanner.nextInt();
+ midiDeviceFound = true;
} catch (FileNotFoundException e) {
Slog.e(TAG, "could not open MIDI file", e);
- enabled = false;
} finally {
if (scanner != null) {
scanner.close();
}
}
}
+ if (!midiDeviceFound) {
+ Slog.e(TAG, "Failed to enable MIDI function");
+ enabled = false;
+ }
}
mMidiEnabled = enabled;
}
@@ -1369,6 +1402,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
case MSG_BOOT_COMPLETED:
operationId = sUsbOperationCount.incrementAndGet();
mBootCompleted = true;
+ if (DEBUG) Slog.d(TAG, "MSG_BOOT_COMPLETED");
finishBoot(operationId);
break;
case MSG_USER_SWITCHED: {
@@ -1415,14 +1449,38 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
}
case MSG_INCREASE_SENDSTRING_COUNT: {
mSendStringCount = mSendStringCount + 1;
+ break;
+ }
+ case MSG_USER_UNLOCKED_AFTER_BOOT: {
+ if (DEBUG) Slog.d(TAG, "MSG_USER_UNLOCKED_AFTER_BOOT");
+ if (mUserUnlockedAfterBoot) {
+ break;
+ }
+ mUserUnlockedAfterBoot = true;
+ if (mCurrentUsbFunctionsReceived && mUserUnlockedAfterBoot) {
+ attachAccessoryAfterBoot();
+ }
+ break;
}
}
}
+ private void attachAccessoryAfterBoot() {
+ if (mCurrentAccessory != null) {
+ Slog.i(TAG, "AccessoryAttached");
+ mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
+ broadcastUsbAccessoryHandshake();
+ } else if (mPendingBootAccessoryHandshakeBroadcast) {
+ broadcastUsbAccessoryHandshake();
+ }
+ mPendingBootAccessoryHandshakeBroadcast = false;
+ }
+
public abstract void handlerInitDone(int operationId);
protected void finishBoot(int operationId) {
if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
+ if (DEBUG) Slog.d(TAG, "finishBoot all flags true");
if (mPendingBootBroadcast) {
updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
mPendingBootBroadcast = false;
@@ -1433,14 +1491,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
} else {
setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
- if (mCurrentAccessory != null) {
- mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
- broadcastUsbAccessoryHandshake();
- } else if (mPendingBootAccessoryHandshakeBroadcast) {
- broadcastUsbAccessoryHandshake();
+ if (!Flags.checkUserActionUnlocked()) {
+ attachAccessoryAfterBoot();
+ }
+ if (Flags.checkUserActionUnlocked() && mUserUnlockedAfterBoot) {
+ attachAccessoryAfterBoot();
}
-
- mPendingBootAccessoryHandshakeBroadcast = false;
updateUsbNotification(false);
updateAdbNotification(false);
updateUsbFunctions();
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
new file mode 100644
index 000000000000..31c5986c45b8
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * UsbManagerInternal provides internal APIs for the UsbService to
+ * reduce IPC overhead costs and support internal USB data signal stakers.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class UsbManagerInternal {
+
+ public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+ public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {OS_USB_DISABLE_REASON_AAPM,
+ OS_USB_DISABLE_REASON_LOCKDOWN_MODE})
+ public @interface OsUsbDisableReason {
+ }
+
+ public abstract boolean enableUsbData(String portId, boolean enable,
+ int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
+
+ public abstract UsbPort[] getPorts();
+
+} \ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9470c0a944c2..ec4f7e1ea4ba 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -46,6 +46,7 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
@@ -69,6 +70,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
+import com.android.server.LocalServices;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
@@ -165,8 +167,10 @@ public class UsbService extends IUsbManager.Stub {
private final Object mLock = new Object();
// Key: USB port id
- // Value: A set of UIDs of requesters who request disabling usb data
- private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+ // Value: UsbDataSignalDisableRequesters: UIDs of requesters who request
+ // disabling usb data and disable request reasons by local service callers
+ private final ArrayMap<String, UsbDataSignalDisableRequesters>
+ mUsbDisableRequesters = new ArrayMap<>();
/**
* @return the {@link UsbUserSettingsManager} for the given userId
@@ -221,6 +225,9 @@ public class UsbService extends IUsbManager.Stub {
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
+ if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+ LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ }
}
// Ideally we should use the injector pattern so we wouldn't need this constructor for test
@@ -236,6 +243,10 @@ public class UsbService extends IUsbManager.Stub {
mUserManager = userManager;
mSettingsManager = usbSettingsManager;
mPermissionManager = new UsbPermissionManager(context, this);
+
+ if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+ LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ }
}
/**
@@ -903,15 +914,21 @@ public class UsbService extends IUsbManager.Stub {
@Override
public boolean enableUsbData(String portId, boolean enable, int operationId,
IUsbOperationInternal callback) {
- return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+ return enableUsbDataInternal(portId, enable, operationId, callback,
+ Binder.getCallingUid(), false);
}
/**
- * Internal function abstracted for testing with callerUid
+ * Manages the enablement of USB data. Requester field could mean two things:
+ * 1. UID of the app that requested USB data to be disabled if caller is external.
+ * 2. Enumberated disable request reason if the caller is internal.
+ *
+ * For internal requests, isInternalRequest should be set to true. Since
+ * internal requests all share the same UID, the request managed separately.
*/
@VisibleForTesting
boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
- IUsbOperationInternal callback, int callerUid) {
+ IUsbOperationInternal callback, int requester, boolean isInternalRequest) {
Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -919,7 +936,7 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
+ if (!shouldUpdateUsbSignaling(portId, enable, requester, isInternalRequest)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -949,25 +966,42 @@ public class UsbService extends IUsbManager.Stub {
}
/**
+ * Function to determine if USB data signaling state should be updated.
+ * Depending on if request is internal, input requester should be UID or enumerated disable
+ * reason.
+ *
* If enable = true, exclude UID from update list.
* If enable = false, include UID in update list.
* Return false if enable = true and the list is empty (no updates).
* Return true otherwise (let downstream decide on updates).
*/
- private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+ private boolean shouldUpdateUsbSignaling(String portId, boolean enable,
+ int requester, boolean isInternalRequest) {
+ if(isInternalRequest &&
+ !android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal())
+ return false;
synchronized (mUsbDisableRequesters) {
if (!mUsbDisableRequesters.containsKey(portId)) {
- mUsbDisableRequesters.put(portId, new ArraySet<>());
+ mUsbDisableRequesters.put(portId, new UsbDataSignalDisableRequesters());
}
-
- ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+ UsbDataSignalDisableRequesters disableRequests =
+ mUsbDisableRequesters.get(portId);
if (enable) {
- uidsOfDisableRequesters.remove(uid);
- // re-enable USB port (return true) if there are no other disable requesters
- return uidsOfDisableRequesters.isEmpty();
+ if(isInternalRequest) {
+ disableRequests.mInternalReasons.remove(requester);
+ } else {
+ disableRequests.mExternalUids.remove(requester);
+ }
+ // re-enable USB port (return true) if there are no other
+ // disable requesters
+ return disableRequests.isEmpty();
} else {
- uidsOfDisableRequesters.add(uid);
+ if(isInternalRequest) {
+ disableRequests.mInternalReasons.add(requester);
+ } else {
+ disableRequests.mExternalUids.add(requester);
+ }
}
}
return true;
@@ -976,7 +1010,8 @@ public class UsbService extends IUsbManager.Stub {
@Override
public void enableUsbDataWhileDocked(String portId, int operationId,
IUsbOperationInternal callback) {
- enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+ enableUsbDataWhileDockedInternal(portId, operationId, callback,
+ Binder.getCallingUid(), false);
}
/**
@@ -984,7 +1019,7 @@ public class UsbService extends IUsbManager.Stub {
*/
@VisibleForTesting
void enableUsbDataWhileDockedInternal(String portId, int operationId,
- IUsbOperationInternal callback, int callerUid) {
+ IUsbOperationInternal callback, int callerUid, boolean isInternalRequest) {
Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback,
@@ -993,7 +1028,7 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
+ if (!shouldUpdateUsbSignaling(portId, true, callerUid, isInternalRequest)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -1455,10 +1490,11 @@ public class UsbService extends IUsbManager.Stub {
public void onUidRemoved(int uid) {
synchronized (mUsbDisableRequesters) {
for (String portId : mUsbDisableRequesters.keySet()) {
- ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
- if (disabledUid != null) {
- disabledUid.remove(uid);
- if (disabledUid.isEmpty()) {
+ UsbDataSignalDisableRequesters disableRequesters =
+ mUsbDisableRequesters.get(portId);
+ if (disableRequesters != null) {
+ disableRequesters.mExternalUids.remove(uid);
+ if (disableRequesters.isEmpty()) {
enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
new IUsbOperationInternal.Default());
}
@@ -1491,9 +1527,27 @@ public class UsbService extends IUsbManager.Stub {
}
mLockdownModeStatus = lockDownTriggeredByUser;
for (UsbPort port: mPortManager.getPorts()) {
- enableUsbData(port.getId(), !lockDownTriggeredByUser, STRONG_AUTH_OPERATION_ID,
- new IUsbOperationInternal.Default());
+ enableUsbDataInternal(port.getId(), !lockDownTriggeredByUser,
+ STRONG_AUTH_OPERATION_ID,
+ new IUsbOperationInternal.Default(),
+ UsbManagerInternal.OS_USB_DISABLE_REASON_LOCKDOWN_MODE,
+ true);
}
}
}
+
+ private class UsbManagerInternalImpl extends UsbManagerInternal {
+ @Override
+ public boolean enableUsbData(String portId, boolean enable,
+ int operationId, IUsbOperationInternal callback,
+ @OsUsbDisableReason int disableReason) {
+ return enableUsbDataInternal(portId, enable, operationId, callback,
+ disableReason, true);
+ }
+
+ @Override
+ public UsbPort[] getPorts() {
+ return mPortManager.getPorts();
+ }
+ }
}
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index cd96d76a1c93..a2d0efd1d063 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -14,3 +14,10 @@ flag {
description: "This flag enables binding to MtpService when in mtp/ptp modes"
bug: "332256525"
}
+
+flag {
+ name: "check_user_action_unlocked"
+ namespace: "usb"
+ description: "This flag checks if phone is unlocked after boot"
+ bug: "73654179"
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index dc5f6e960a2b..01ff67400eb1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -842,7 +842,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return;
}
- model.setRequested(config.allowMultipleTriggers);
+ model.setRequested(config.isMultipleTriggersAllowed());
// TODO: Remove this block if the lower layer supports multiple triggers.
if (model.isRequested()) {
updateRecognitionLocked(model, true);
@@ -964,7 +964,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
RecognitionConfig config = modelData.getRecognitionConfig();
if (config != null) {
// Whether we should continue by starting this again.
- modelData.setRequested(config.allowMultipleTriggers);
+ modelData.setRequested(config.isMultipleTriggersAllowed());
}
// TODO: Remove this block if the lower layer supports multiple triggers.
if (modelData.isRequested()) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 862aff9be9ce..e0cdbddd2efb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -504,6 +504,11 @@ public class SoundTriggerService extends SystemService {
pw.println("\n##Sound Model Stats dump:\n");
mSoundModelStatTracker.dump(pw);
}
+
+ @Override
+ protected void onUnhandledException(int code, int flags, Exception e) {
+ Slog.wtf(TAG, "Unhandled exception code: " + code, e);
+ }
}
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
@@ -687,6 +692,7 @@ public class SoundTriggerService extends SystemService {
@Override
public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
+ final UserHandle userHandle = Binder.getCallingUserHandle();
mEventLogger.enqueue(new SessionEvent(Type.START_RECOGNITION_SERVICE,
getUuid(soundModelId)));
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
@@ -699,7 +705,7 @@ public class SoundTriggerService extends SystemService {
IRecognitionStatusCallback callback =
new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
- detectionService, Binder.getCallingUserHandle(), config);
+ detectionService, userHandle, config);
synchronized (mLock) {
SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
@@ -1439,7 +1445,7 @@ public class SoundTriggerService extends SystemService {
runOrAddOperation(new Operation(
// always execute:
() -> {
- if (!mRecognitionConfig.allowMultipleTriggers) {
+ if (!mRecognitionConfig.isMultipleTriggersAllowed()) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index b1165bb57628..dfd80a00544d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -36,6 +36,8 @@ import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
+import com.android.server.FgThread;
+
public class SoundTriggerHw3Compat implements ISoundTriggerHal {
private final @NonNull ISoundTriggerHw mDriver;
private final @NonNull Runnable mRebootRunnable;
@@ -217,7 +219,12 @@ public class SoundTriggerHw3Compat implements ISoundTriggerHal {
@Override
public void onResourcesAvailable() {
- mDelegate.onResourcesAvailable();
+ // This call does not need to be sequenced relative to sessions on the upper levels.
+ // That is, if a new session gets this callback or if a already detached session gets
+ // this callback, because it is delayed, it doesn't matter, since this callback is
+ // purely informative and does not mutate any state -- it merely causes an already legal
+ // operation to be possibly re-attempted.
+ FgThread.getExecutor().execute(mDelegate::onResourcesAvailable);
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 45a7fafa90a7..07969bd2cd43 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -304,11 +304,17 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
@Override
public void unloadModel(int modelHandle) {
synchronized (SoundTriggerModule.this) {
- int sessionId;
checkValid();
- sessionId = mLoadedModels.get(modelHandle).unload();
- mAudioSessionProvider.releaseSession(sessionId);
+ final var session = mLoadedModels.get(modelHandle).getSession();
+ mLoadedModels.remove(modelHandle);
+ mAudioSessionProvider.releaseSession(session.mSessionHandle);
}
+ // We don't need to post-synchronize on anything once the HAL has finished the unload
+ // and dispatched any appropriate callbacks -- since we don't do any state checking
+ // onModelUnloaded regardless.
+ // This is generally safe since there is no post-condition on the framework side when
+ // a model is unloaded. We assume that we won't ever have a modelHandle collision.
+ mHalService.unloadSoundModel(modelHandle);
}
@Override
@@ -402,6 +408,10 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
return mState;
}
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession getSession() {
+ return mSession;
+ }
+
private void setState(@NonNull ModelState state) {
mState = state;
SoundTriggerModule.this.notifyAll();
@@ -426,16 +436,6 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
return mHandle;
}
- /**
- * Unloads the model.
- * @return The audio session handle.
- */
- private int unload() {
- mHalService.unloadSoundModel(mHandle);
- mLoadedModels.remove(mHandle);
- return mSession.mSessionHandle;
- }
-
private IBinder startRecognition(@NonNull RecognitionConfig config) {
if (mIsStopping == true) {
throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred");